Move buffering logic from RageFile into RageFileObj.
Common file interface class, RageBasicFile, shared by RageFile and RageFileObj. This makes most uses of these objects interchangeable. RageFile is now just a simple wrapper for RageFileObj, to create files in FILEMAN's namespace; file objects can also be created independently. This means that, for example, IniFile can be used to write a file to a CString, without having to jump through hoops, and without having to use a separate file access wrapper; just do something like: RageFileObjMem string_file; ini.WriteFile( string ); const CString &sString = string_file.GetString();
This commit is contained in:
+71
-299
@@ -1,3 +1,10 @@
|
||||
/*
|
||||
* This provides an interface to open files in RageFileManager's namespace
|
||||
* This is just a simple RageBasicFile wrapper on top of another RageBasicFile;
|
||||
* when a file is open, is acts like the underlying RageBasicFile, except that
|
||||
* a few extra sanity checks are made to check file modes.
|
||||
*/
|
||||
|
||||
#include "global.h"
|
||||
#include "RageFile.h"
|
||||
#include "RageUtil.h"
|
||||
@@ -6,26 +13,15 @@
|
||||
RageFile::RageFile()
|
||||
{
|
||||
m_File = NULL;
|
||||
m_BufAvail = 0;
|
||||
m_EOF = false;
|
||||
m_FilePos = 0;
|
||||
}
|
||||
|
||||
RageFile::RageFile( const RageFile &cpy )
|
||||
RageFile::RageFile( const RageFile &cpy ):
|
||||
RageBasicFile( cpy )
|
||||
{
|
||||
ResetBuf();
|
||||
|
||||
/* This will copy the file driver, including its internal file pointer. */
|
||||
m_File = FILEMAN->CopyFileObj( cpy.m_File );
|
||||
m_Path = cpy.m_Path;
|
||||
m_Mode = cpy.m_Mode;
|
||||
m_Error = cpy.m_Error;
|
||||
m_EOF = cpy.m_EOF;
|
||||
m_FilePos = cpy.m_FilePos;
|
||||
memcpy( this->m_Buffer, cpy.m_Buffer, sizeof(m_Buffer) );
|
||||
m_pBuf = m_Buffer + (cpy.m_pBuf-cpy.m_Buffer);
|
||||
|
||||
m_BufAvail = cpy.m_BufAvail;
|
||||
}
|
||||
|
||||
CString RageFile::GetPath() const
|
||||
@@ -49,9 +45,6 @@ bool RageFile::Open( const CString& path, int mode )
|
||||
FixSlashesInPlace(m_Path);
|
||||
|
||||
m_Mode = mode;
|
||||
m_EOF = false;
|
||||
m_FilePos = 0;
|
||||
ResetBuf();
|
||||
|
||||
if( (m_Mode&READ) && (m_Mode&WRITE) )
|
||||
{
|
||||
@@ -83,291 +76,90 @@ void RageFile::Close()
|
||||
m_File = NULL;
|
||||
}
|
||||
|
||||
/* Fill the internal buffer. This never marks EOF, since this is an internal, hidden
|
||||
* read; EOF should only be set as a result of a real read. (That is, disabling buffering
|
||||
* shouldn't cause the results of AtEOF to change.) */
|
||||
int RageFile::FillBuf()
|
||||
{
|
||||
/* The buffer starts at m_Buffer; any data in it starts at m_pBuf; space between
|
||||
* the two is old data that we've read. (Don't mangle that data; we can use it
|
||||
* for seeking backwards.) */
|
||||
const int iBufAvail = sizeof(m_Buffer) - (m_pBuf-m_Buffer) - m_BufAvail;
|
||||
ASSERT_M( iBufAvail >= 0, ssprintf("%p, %p, %i", m_pBuf, m_Buffer, (int) sizeof(m_Buffer) ) );
|
||||
const int size = m_File->Read( m_pBuf+m_BufAvail, iBufAvail );
|
||||
|
||||
if( size == -1 )
|
||||
SetError( m_File->GetError() );
|
||||
|
||||
if( size > 0 )
|
||||
m_BufAvail += size;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
void RageFile::ResetBuf()
|
||||
{
|
||||
m_BufAvail = 0;
|
||||
m_pBuf = m_Buffer;
|
||||
}
|
||||
|
||||
/* Read up to the next \n, and return it in out. Strip the \n. If the \n is
|
||||
* preceded by a \r (DOS newline), strip that, too. */
|
||||
#define ASSERT_OPEN ASSERT_M( IsOpen(), ssprintf("\"%s\" is not open.", m_Path.c_str()) );
|
||||
#define ASSERT_READ ASSERT_OPEN; ASSERT_M( !!(m_Mode&READ), ssprintf("\"%s\" is not open for reading", m_Path.c_str()) );
|
||||
#define ASSERT_WRITE ASSERT_OPEN; ASSERT_M( !!(m_Mode&WRITE), ssprintf("\"%s\" is not open for writing", m_Path.c_str()) );
|
||||
int RageFile::GetLine( CString &out )
|
||||
{
|
||||
out = "";
|
||||
|
||||
if ( !IsOpen() )
|
||||
RageException::Throw("\"%s\" is not open.", m_Path.c_str());
|
||||
|
||||
if( !(m_Mode&READ) )
|
||||
RageException::Throw("\"%s\" is not open for reading", GetPath().c_str());
|
||||
|
||||
if( m_EOF )
|
||||
return 0;
|
||||
|
||||
bool GotData = false;
|
||||
while( 1 )
|
||||
{
|
||||
bool done = false;
|
||||
|
||||
/* Find the end of the block we'll move to out. */
|
||||
char *p = (char *) memchr( m_pBuf, '\n', m_BufAvail );
|
||||
bool ReAddCR = false;
|
||||
if( p == NULL )
|
||||
{
|
||||
/* Hack: If the last character of the buffer is \r, then it's likely that an
|
||||
* \r\n has been split across buffers. Move everything else, then move the
|
||||
* \r to the beginning of the buffer and handle it the next time around the loop. */
|
||||
if( m_BufAvail && m_pBuf[m_BufAvail-1] == '\r' )
|
||||
{
|
||||
ReAddCR = true;
|
||||
--m_BufAvail;
|
||||
}
|
||||
|
||||
p = m_pBuf+m_BufAvail; /* everything */
|
||||
}
|
||||
else
|
||||
done = true;
|
||||
|
||||
if( p >= m_pBuf )
|
||||
{
|
||||
char *RealEnd = p;
|
||||
if( done && p > m_pBuf && p[-1] == '\r' )
|
||||
--RealEnd; /* not including \r */
|
||||
out.append( m_pBuf, RealEnd );
|
||||
|
||||
if( done )
|
||||
++p; /* skip \n */
|
||||
|
||||
const int used = p-m_pBuf;
|
||||
if( used )
|
||||
{
|
||||
m_BufAvail -= used;
|
||||
m_FilePos += used;
|
||||
GotData = true;
|
||||
m_pBuf = p;
|
||||
}
|
||||
}
|
||||
|
||||
if( ReAddCR )
|
||||
{
|
||||
ASSERT( m_BufAvail == 0 );
|
||||
m_pBuf = m_Buffer;
|
||||
m_Buffer[m_BufAvail] = '\r';
|
||||
++m_BufAvail;
|
||||
}
|
||||
|
||||
if( done )
|
||||
break;
|
||||
|
||||
/* We need more data. */
|
||||
m_pBuf = m_Buffer;
|
||||
|
||||
const int size = FillBuf();
|
||||
|
||||
/* If we've read data already, then don't mark EOF yet. Wait until the
|
||||
* next time we're called. */
|
||||
if( size == 0 && !GotData )
|
||||
{
|
||||
m_EOF = true;
|
||||
return 0;
|
||||
}
|
||||
if( size == -1 )
|
||||
return -1; // error
|
||||
if( size == 0 )
|
||||
break; // EOF or error
|
||||
}
|
||||
return GotData? 1:0;
|
||||
ASSERT_READ;
|
||||
return m_File->GetLine( out );
|
||||
}
|
||||
|
||||
// Always use "\r\n". Even though the program may be running on Unix, the
|
||||
// files written to a memory card are likely to be edited using Windows.
|
||||
//#if defined(_WIN32)
|
||||
#define NEWLINE "\r\n"
|
||||
//#else
|
||||
//#define NEWLINE "\n"
|
||||
//#endif
|
||||
|
||||
int RageFile::PutLine( const CString &str )
|
||||
{
|
||||
if( Write(str) == -1 )
|
||||
return -1;
|
||||
return Write( CString(NEWLINE) );
|
||||
ASSERT_WRITE;
|
||||
return m_File->PutLine( str );
|
||||
}
|
||||
|
||||
bool RageFile::AtEOF() const
|
||||
{
|
||||
ASSERT_READ;
|
||||
return m_File->AtEOF();
|
||||
}
|
||||
|
||||
void RageFile::ClearError()
|
||||
{
|
||||
if( m_File != NULL )
|
||||
m_File->ClearError();
|
||||
m_sError = "";
|
||||
}
|
||||
|
||||
CString RageFile::GetError() const
|
||||
{
|
||||
if( m_File != NULL && m_File->GetError() != "" )
|
||||
return m_File->GetError();
|
||||
return m_sError;
|
||||
}
|
||||
|
||||
|
||||
int RageFile::Read( void *buffer, size_t bytes )
|
||||
void RageFile::SetError( const CString &err )
|
||||
{
|
||||
if( !IsOpen() )
|
||||
RageException::Throw("\"%s\" is not open.", GetPath().c_str());
|
||||
if( m_File != NULL )
|
||||
m_File->ClearError();
|
||||
m_sError = err;
|
||||
}
|
||||
|
||||
if( !(m_Mode&READ) )
|
||||
RageException::Throw("\"%s\" is not open for reading", GetPath().c_str());
|
||||
|
||||
int ret = 0;
|
||||
|
||||
while( !m_EOF && bytes > 0 )
|
||||
{
|
||||
/* Copy data out of the buffer first. */
|
||||
int FromBuffer = min( (int) bytes, m_BufAvail );
|
||||
memcpy( buffer, m_pBuf, FromBuffer );
|
||||
|
||||
ret += FromBuffer;
|
||||
m_FilePos += FromBuffer;
|
||||
bytes -= FromBuffer;
|
||||
m_BufAvail -= FromBuffer;
|
||||
m_pBuf += FromBuffer;
|
||||
|
||||
buffer = (char *) buffer + FromBuffer;
|
||||
|
||||
if( !bytes )
|
||||
break;
|
||||
|
||||
ASSERT( !m_BufAvail );
|
||||
|
||||
/* We need more; either fill the buffer and keep going, or just read directly
|
||||
* into the destination buffer. */
|
||||
if( bytes >= sizeof(m_Buffer) )
|
||||
{
|
||||
/* We have a lot more to read, so don't waste time copying it into the
|
||||
* buffer. */
|
||||
int FromFile = m_File->Read( buffer, bytes );
|
||||
if( FromFile == -1 )
|
||||
{
|
||||
SetError( m_File->GetError() );
|
||||
return -1;
|
||||
}
|
||||
if( FromFile == 0 )
|
||||
m_EOF = true;
|
||||
ret += FromFile;
|
||||
m_FilePos += FromFile;
|
||||
return ret;
|
||||
}
|
||||
|
||||
m_pBuf = m_Buffer;
|
||||
int got = FillBuf();
|
||||
if( got < 0 )
|
||||
return got;
|
||||
if( got == 0 )
|
||||
m_EOF = true;
|
||||
}
|
||||
|
||||
return ret;
|
||||
int RageFile::Read( void *pBuffer, size_t iBytes )
|
||||
{
|
||||
ASSERT_READ;
|
||||
return m_File->Read( pBuffer, iBytes );
|
||||
}
|
||||
|
||||
int RageFile::Seek( int offset )
|
||||
{
|
||||
if( !IsOpen() )
|
||||
RageException::Throw("\"%s\" is not open.", GetPath().c_str());
|
||||
ASSERT_READ;
|
||||
return m_File->Seek( offset );
|
||||
}
|
||||
|
||||
if( !(m_Mode&READ) )
|
||||
RageException::Throw("\"%s\" is not open for reading; can't seek", GetPath().c_str());
|
||||
|
||||
m_EOF = false;
|
||||
|
||||
ResetBuf();
|
||||
|
||||
int pos = m_File->Seek( offset );
|
||||
if( pos == -1 )
|
||||
{
|
||||
SetError( m_File->GetError() );
|
||||
return -1;
|
||||
}
|
||||
|
||||
m_FilePos = pos;
|
||||
return pos;
|
||||
int RageFile::Tell() const
|
||||
{
|
||||
ASSERT_READ;
|
||||
return m_File->Tell();
|
||||
}
|
||||
|
||||
int RageFile::GetFileSize() const
|
||||
{
|
||||
if( !IsOpen() )
|
||||
RageException::Throw("\"%s\" is not open.", GetPath().c_str());
|
||||
|
||||
if( !(m_Mode&READ) )
|
||||
RageException::Throw("\"%s\" is not open for reading; can't GetFileSize", GetPath().c_str());
|
||||
|
||||
/* GetFileSize() may need to do non-const-like things--the default implementation reads
|
||||
* until the end of file to emulate it. However, it should always restore the state to
|
||||
* the way it was, so pretend it's const. */
|
||||
int iRet = const_cast<RageFileObj*>(m_File)->GetFileSize();
|
||||
ASSERT_M( iRet >= 0, ssprintf("%i", iRet) );
|
||||
return iRet;
|
||||
ASSERT_READ;
|
||||
return m_File->GetFileSize();
|
||||
}
|
||||
|
||||
int RageFile::Read( CString &buffer, int bytes )
|
||||
{
|
||||
buffer.erase( buffer.begin(), buffer.end() );
|
||||
buffer.reserve( bytes != -1? bytes: this->GetFileSize() );
|
||||
|
||||
int ret = 0;
|
||||
char buf[4096];
|
||||
while( bytes == -1 || ret < bytes )
|
||||
{
|
||||
int ToRead = sizeof(buf);
|
||||
if( bytes != -1 )
|
||||
ToRead = min( ToRead, bytes-ret );
|
||||
|
||||
const int got = Read( buf, ToRead );
|
||||
if( got == 0 )
|
||||
break;
|
||||
if( got == -1 )
|
||||
return -1;
|
||||
|
||||
buffer.append( buf, got );
|
||||
ret += got;
|
||||
}
|
||||
|
||||
return ret;
|
||||
ASSERT_READ;
|
||||
return m_File->Read( buffer, bytes );
|
||||
}
|
||||
|
||||
int RageFile::Write( const void *buffer, size_t bytes )
|
||||
{
|
||||
if( !IsOpen() )
|
||||
RageException::Throw("\"%s\" is not open.", GetPath().c_str());
|
||||
|
||||
if( !(m_Mode&WRITE) )
|
||||
RageException::Throw("\"%s\" is not open for writing", GetPath().c_str());
|
||||
|
||||
int iRet = m_File->Write( buffer, bytes );
|
||||
if( iRet == -1 )
|
||||
{
|
||||
SetError( m_File->GetError() );
|
||||
return -1;
|
||||
}
|
||||
return iRet;
|
||||
ASSERT_WRITE;
|
||||
return m_File->Write( buffer, bytes );
|
||||
}
|
||||
|
||||
|
||||
int RageFile::Write( const void *buffer, size_t bytes, int nmemb )
|
||||
{
|
||||
/* Simple write. We never return partial writes. */
|
||||
int ret = Write( buffer, bytes*nmemb ) / bytes;
|
||||
if( ret == -1 )
|
||||
{
|
||||
SetError( m_File->GetError() );
|
||||
return -1;
|
||||
}
|
||||
return ret / bytes;
|
||||
ASSERT_WRITE;
|
||||
return m_File->Write( buffer, bytes, nmemb );
|
||||
}
|
||||
|
||||
int RageFile::Flush()
|
||||
@@ -378,42 +170,22 @@ int RageFile::Flush()
|
||||
return -1;
|
||||
}
|
||||
|
||||
int iRet = m_File->Flush();
|
||||
if( iRet == -1 )
|
||||
{
|
||||
SetError( m_File->GetError() );
|
||||
return -1;
|
||||
}
|
||||
return iRet;
|
||||
return m_File->Flush();
|
||||
}
|
||||
|
||||
int RageFile::Read( void *buffer, size_t bytes, int nmemb )
|
||||
{
|
||||
const int ret = Read( buffer, bytes*nmemb );
|
||||
if( ret == -1 )
|
||||
return -1;
|
||||
|
||||
/* If we're reading 10-byte blocks, and we got 27 bytes, we have 7 extra bytes.
|
||||
* Seek back. */
|
||||
const int extra = ret % bytes;
|
||||
Seek( Tell()-extra );
|
||||
|
||||
return ret/bytes;
|
||||
ASSERT_READ;
|
||||
return m_File->Read( buffer, bytes, nmemb );
|
||||
}
|
||||
|
||||
int RageFile::Seek( int offset, int whence )
|
||||
{
|
||||
switch( whence )
|
||||
{
|
||||
case SEEK_CUR:
|
||||
return Seek( Tell() + offset );
|
||||
case SEEK_END:
|
||||
offset += GetFileSize();
|
||||
}
|
||||
return Seek( (int) offset );
|
||||
ASSERT_READ;
|
||||
return m_File->Seek( offset, whence );
|
||||
}
|
||||
|
||||
void FileReading::ReadBytes( RageFile &f, void *buf, int size, CString &sError )
|
||||
void FileReading::ReadBytes( RageBasicFile &f, void *buf, int size, CString &sError )
|
||||
{
|
||||
if( sError.size() != 0 )
|
||||
return;
|
||||
@@ -425,7 +197,7 @@ void FileReading::ReadBytes( RageFile &f, void *buf, int size, CString &sError )
|
||||
sError = "Unexpected end of file";
|
||||
}
|
||||
|
||||
uint8_t FileReading::read_8( RageFile &f, CString &sError )
|
||||
uint8_t FileReading::read_8( RageBasicFile &f, CString &sError )
|
||||
{
|
||||
uint8_t val;
|
||||
ReadBytes( f, &val, sizeof(uint8_t), sError );
|
||||
@@ -435,7 +207,7 @@ uint8_t FileReading::read_8( RageFile &f, CString &sError )
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint16_t FileReading::read_u16_le( RageFile &f, CString &sError )
|
||||
uint16_t FileReading::read_u16_le( RageBasicFile &f, CString &sError )
|
||||
{
|
||||
uint16_t val;
|
||||
ReadBytes( f, &val, sizeof(uint16_t), sError );
|
||||
@@ -445,7 +217,7 @@ uint16_t FileReading::read_u16_le( RageFile &f, CString &sError )
|
||||
return 0;
|
||||
}
|
||||
|
||||
int16_t FileReading::read_16_le( RageFile &f, CString &sError )
|
||||
int16_t FileReading::read_16_le( RageBasicFile &f, CString &sError )
|
||||
{
|
||||
int16_t val;
|
||||
ReadBytes( f, &val, sizeof(int16_t), sError );
|
||||
@@ -455,7 +227,7 @@ int16_t FileReading::read_16_le( RageFile &f, CString &sError )
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t FileReading::read_u32_le( RageFile &f, CString &sError )
|
||||
uint32_t FileReading::read_u32_le( RageBasicFile &f, CString &sError )
|
||||
{
|
||||
uint32_t val;
|
||||
ReadBytes( f, &val, sizeof(uint32_t), sError );
|
||||
@@ -465,7 +237,7 @@ uint32_t FileReading::read_u32_le( RageFile &f, CString &sError )
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t FileReading::read_32_le( RageFile &f, CString &sError )
|
||||
int32_t FileReading::read_32_le( RageBasicFile &f, CString &sError )
|
||||
{
|
||||
int32_t val;
|
||||
ReadBytes( f, &val, sizeof(int32_t), sError );
|
||||
|
||||
+66
-33
@@ -7,10 +7,55 @@
|
||||
|
||||
class RageFileObj;
|
||||
|
||||
class RageFile
|
||||
{
|
||||
friend class RageFileObj;
|
||||
|
||||
/* This is a simple file I/O interface. Although most of these operations
|
||||
* are straightforward, there are several of them; most of the time, you'll
|
||||
* only want to implement RageFileObj. */
|
||||
class RageBasicFile
|
||||
{
|
||||
public:
|
||||
virtual ~RageBasicFile() { }
|
||||
|
||||
virtual CString GetError() const = 0;
|
||||
virtual void ClearError() = 0;
|
||||
virtual bool AtEOF() const = 0;
|
||||
|
||||
/* Seek to the given absolute offset. Return to the position actually
|
||||
* seeked to; if the position given was beyond the end of the file, the
|
||||
* return value will be the size of the file. */
|
||||
virtual int Seek( int iOffset ) = 0;
|
||||
virtual int Seek( int offset, int whence ) = 0;
|
||||
virtual int Tell() const = 0;
|
||||
|
||||
/* Read at most iSize bytes into pBuf. Return the number of bytes read,
|
||||
* 0 on end of stream, or -1 on error. Note that reading less than iSize
|
||||
* does not necessarily mean that the end of the stream has been reached;
|
||||
* keep reading until 0 is returned. */
|
||||
virtual int Read( void *pBuffer, size_t iBytes ) = 0;
|
||||
virtual int Read( CString &buffer, int bytes = -1 ) = 0;
|
||||
virtual int Read( void *buffer, size_t bytes, int nmemb ) = 0;
|
||||
|
||||
/* Write iSize bytes of data from pBuf. Return 0 on success, -1 on error. */
|
||||
virtual int Write( const void *pBuffer, size_t iBytes ) = 0;
|
||||
virtual int Write( const CString &sString ) = 0;
|
||||
virtual int Write( const void *buffer, size_t bytes, int nmemb ) = 0;
|
||||
|
||||
/* Due to buffering, writing may not happen by the end of a Write() call, so not
|
||||
* all errors may be returned by it. Data will be flushed when the stream (or its
|
||||
* underlying object) is destroyed, but errors can no longer be returned. Call
|
||||
* Flush() to flush pending data, in order to check for errors. */
|
||||
virtual int Flush() = 0;
|
||||
|
||||
virtual int GetLine( CString &out ) = 0;
|
||||
virtual int PutLine( const CString &str ) = 0;
|
||||
|
||||
virtual int GetFileSize() const = 0;
|
||||
};
|
||||
|
||||
/* This is the high-level interface, which interfaces with RageFileObj implementations
|
||||
* and RageFileManager. */
|
||||
class RageFile: public RageBasicFile
|
||||
{
|
||||
public:
|
||||
enum
|
||||
{
|
||||
@@ -25,8 +70,8 @@ public:
|
||||
SLOW_FLUSH = 0x8
|
||||
};
|
||||
|
||||
RageFile();
|
||||
~RageFile() { Close(); }
|
||||
RageFile();
|
||||
~RageFile() { Close(); }
|
||||
RageFile( const RageFile &cpy );
|
||||
|
||||
/* Use GetRealPath to get the path this file was opened with; use that if you
|
||||
@@ -38,17 +83,16 @@ public:
|
||||
const CString &GetRealPath() const { return m_Path; }
|
||||
CString GetPath() const;
|
||||
|
||||
bool Open( const CString& path, int mode = READ );
|
||||
void Close();
|
||||
|
||||
bool Open( const CString& path, int mode = READ );
|
||||
void Close();
|
||||
bool IsOpen() const { return m_File != NULL; }
|
||||
int GetOpenMode() const { return m_Mode; }
|
||||
bool AtEOF() const { return m_EOF; }
|
||||
CString GetError() const { return m_Error; }
|
||||
void ClearError() { m_Error = ""; }
|
||||
bool IsGood() const { return IsOpen() && !AtEOF() && m_Error.empty(); }
|
||||
|
||||
int Tell() const { return m_FilePos; }
|
||||
bool AtEOF() const;
|
||||
CString GetError() const;
|
||||
void ClearError();
|
||||
bool IsGood() const { return IsOpen() && !AtEOF() && GetError().empty(); }
|
||||
|
||||
int Tell() const;
|
||||
int Seek( int offset );
|
||||
int GetFileSize() const;
|
||||
|
||||
@@ -69,24 +113,13 @@ public:
|
||||
int PutLine( const CString &str );
|
||||
|
||||
protected:
|
||||
void SetError( const CString &err ) { m_Error = err; } /* called by RageFileObj::SetError */
|
||||
void SetError( const CString &err );
|
||||
|
||||
private:
|
||||
int FillBuf();
|
||||
void ResetBuf();
|
||||
|
||||
RageFileObj *m_File;
|
||||
CString m_Path;
|
||||
CString m_sError;
|
||||
int m_Mode;
|
||||
|
||||
CString m_Error;
|
||||
bool m_EOF;
|
||||
int m_FilePos;
|
||||
|
||||
enum { BSIZE = 1024 };
|
||||
char m_Buffer[BSIZE];
|
||||
char *m_pBuf;
|
||||
int m_BufAvail;
|
||||
};
|
||||
|
||||
/* Convenience wrappers for reading binary files. */
|
||||
@@ -94,12 +127,12 @@ namespace FileReading
|
||||
{
|
||||
/* On error, these set sError to the error message. If sError is already
|
||||
* non-empty, nothing happens. */
|
||||
void ReadBytes( RageFile &f, void *buf, int size, CString &sError );
|
||||
uint8_t read_8( RageFile &f, CString &sError );
|
||||
int16_t read_16_le( RageFile &f, CString &sError );
|
||||
uint16_t read_u16_le( RageFile &f, CString &sError );
|
||||
int32_t read_32_le( RageFile &f, CString &sError );
|
||||
uint32_t read_u32_le( RageFile &f, CString &sError );
|
||||
void ReadBytes( RageBasicFile &f, void *buf, int size, CString &sError );
|
||||
uint8_t read_8( RageBasicFile &f, CString &sError );
|
||||
int16_t read_16_le( RageBasicFile &f, CString &sError );
|
||||
uint16_t read_u16_le( RageBasicFile &f, CString &sError );
|
||||
int32_t read_32_le( RageBasicFile &f, CString &sError );
|
||||
uint32_t read_u32_le( RageBasicFile &f, CString &sError );
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -91,14 +91,129 @@ RageFileDriver *MakeFileDriver( CString Type, CString Root )
|
||||
}
|
||||
|
||||
|
||||
RageFileObj::RageFileObj()
|
||||
{
|
||||
ResetBuf();
|
||||
|
||||
m_iBufAvail = 0;
|
||||
m_bEOF = false;
|
||||
m_iFilePos = 0;
|
||||
}
|
||||
|
||||
int RageFileObj::Seek( int iOffset )
|
||||
{
|
||||
return SeekInternal( iOffset );
|
||||
m_bEOF = false;
|
||||
|
||||
ResetBuf();
|
||||
|
||||
int iPos = SeekInternal( iOffset );
|
||||
if( iPos != -1 )
|
||||
m_iFilePos = iPos;
|
||||
return iPos;
|
||||
}
|
||||
|
||||
int RageFileObj::Seek( int offset, int whence )
|
||||
{
|
||||
switch( whence )
|
||||
{
|
||||
case SEEK_CUR:
|
||||
return Seek( Tell() + offset );
|
||||
case SEEK_END:
|
||||
offset += GetFileSize();
|
||||
}
|
||||
return Seek( (int) offset );
|
||||
}
|
||||
|
||||
int RageFileObj::Read( void *pBuffer, size_t iBytes )
|
||||
{
|
||||
return ReadInternal( pBuffer, iBytes );
|
||||
int ret = 0;
|
||||
|
||||
while( !m_bEOF && iBytes > 0 )
|
||||
{
|
||||
/* Copy data out of the buffer first. */
|
||||
int FromBuffer = min( (int) iBytes, m_iBufAvail );
|
||||
memcpy( pBuffer, m_pBuf, FromBuffer );
|
||||
|
||||
ret += FromBuffer;
|
||||
m_iFilePos += FromBuffer;
|
||||
iBytes -= FromBuffer;
|
||||
m_iBufAvail -= FromBuffer;
|
||||
m_pBuf += FromBuffer;
|
||||
|
||||
pBuffer = (char *) pBuffer + FromBuffer;
|
||||
|
||||
if( !iBytes )
|
||||
break;
|
||||
|
||||
ASSERT( m_iBufAvail == 0 );
|
||||
|
||||
/* We need more; either fill the buffer and keep going, or just read directly
|
||||
* into the destination buffer. */
|
||||
if( iBytes >= sizeof(m_Buffer) )
|
||||
{
|
||||
/* We have a lot more to read, so don't waste time copying it into the
|
||||
* buffer. */
|
||||
int iFromFile = this->ReadInternal( pBuffer, iBytes );
|
||||
if( iFromFile == -1 )
|
||||
return -1;
|
||||
|
||||
if( iFromFile == 0 )
|
||||
m_bEOF = true;
|
||||
ret += iFromFile;
|
||||
m_iFilePos += iFromFile;
|
||||
return ret;
|
||||
}
|
||||
|
||||
m_pBuf = m_Buffer;
|
||||
int got = FillBuf();
|
||||
if( got < 0 )
|
||||
return got;
|
||||
if( got == 0 )
|
||||
m_bEOF = true;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int RageFileObj::Read( CString &sBuffer, int iBytes )
|
||||
{
|
||||
sBuffer.erase( sBuffer.begin(), sBuffer.end() );
|
||||
sBuffer.reserve( iBytes != -1? iBytes: this->GetFileSize() );
|
||||
|
||||
int iRet = 0;
|
||||
char buf[4096];
|
||||
while( iBytes == -1 || iRet < iBytes )
|
||||
{
|
||||
int ToRead = sizeof(buf);
|
||||
if( iBytes != -1 )
|
||||
ToRead = min( ToRead, iBytes-iRet );
|
||||
|
||||
const int iGot = Read( buf, ToRead );
|
||||
if( iGot == 0 )
|
||||
break;
|
||||
if( iGot == -1 )
|
||||
return -1;
|
||||
|
||||
sBuffer.append( buf, iGot );
|
||||
iRet += iGot;
|
||||
}
|
||||
|
||||
return iRet;
|
||||
}
|
||||
|
||||
int RageFileObj::Read( void *buffer, size_t bytes, int nmemb )
|
||||
{
|
||||
const int iRet = Read( buffer, bytes*nmemb );
|
||||
if( iRet == -1 )
|
||||
return -1;
|
||||
|
||||
/* If we're reading 10-byte blocks, and we got 27 bytes, we have 7 extra bytes.
|
||||
* Seek back. XXX: seeking is very slow for eg. deflated ZIPs. If the block is
|
||||
* small enough, we may be able to stuff the extra data into the buffer. */
|
||||
const int iExtra = iRet % bytes;
|
||||
Seek( Tell()-iExtra );
|
||||
|
||||
return iRet/bytes;
|
||||
}
|
||||
|
||||
int RageFileObj::Write( const void *pBuffer, size_t iBytes )
|
||||
@@ -106,11 +221,144 @@ int RageFileObj::Write( const void *pBuffer, size_t iBytes )
|
||||
return WriteInternal( pBuffer, iBytes );
|
||||
}
|
||||
|
||||
int RageFileObj::Write( const void *buffer, size_t bytes, int nmemb )
|
||||
{
|
||||
/* Simple write. We never return partial writes. */
|
||||
int ret = Write( buffer, bytes*nmemb ) / bytes;
|
||||
if( ret == -1 )
|
||||
return -1;
|
||||
return ret / bytes;
|
||||
}
|
||||
|
||||
int RageFileObj::Flush()
|
||||
{
|
||||
return FlushInternal();
|
||||
}
|
||||
|
||||
|
||||
/* Read up to the next \n, and return it in out. Strip the \n. If the \n is
|
||||
* preceded by a \r (DOS newline), strip that, too. */
|
||||
int RageFileObj::GetLine( CString &out )
|
||||
{
|
||||
out = "";
|
||||
|
||||
if( m_bEOF )
|
||||
return 0;
|
||||
|
||||
bool GotData = false;
|
||||
while( 1 )
|
||||
{
|
||||
bool done = false;
|
||||
|
||||
/* Find the end of the block we'll move to out. */
|
||||
char *p = (char *) memchr( m_pBuf, '\n', m_iBufAvail );
|
||||
bool ReAddCR = false;
|
||||
if( p == NULL )
|
||||
{
|
||||
/* Hack: If the last character of the buffer is \r, then it's likely that an
|
||||
* \r\n has been split across buffers. Move everything else, then move the
|
||||
* \r to the beginning of the buffer and handle it the next time around the loop. */
|
||||
if( m_iBufAvail && m_pBuf[m_iBufAvail-1] == '\r' )
|
||||
{
|
||||
ReAddCR = true;
|
||||
--m_iBufAvail;
|
||||
}
|
||||
|
||||
p = m_pBuf+m_iBufAvail; /* everything */
|
||||
}
|
||||
else
|
||||
done = true;
|
||||
|
||||
if( p >= m_pBuf )
|
||||
{
|
||||
char *RealEnd = p;
|
||||
if( done && p > m_pBuf && p[-1] == '\r' )
|
||||
--RealEnd; /* not including \r */
|
||||
out.append( m_pBuf, RealEnd );
|
||||
|
||||
if( done )
|
||||
++p; /* skip \n */
|
||||
|
||||
const int used = p-m_pBuf;
|
||||
if( used )
|
||||
{
|
||||
m_iBufAvail -= used;
|
||||
m_iFilePos += used;
|
||||
GotData = true;
|
||||
m_pBuf = p;
|
||||
}
|
||||
}
|
||||
|
||||
if( ReAddCR )
|
||||
{
|
||||
ASSERT( m_iBufAvail == 0 );
|
||||
m_pBuf = m_Buffer;
|
||||
m_Buffer[m_iBufAvail] = '\r';
|
||||
++m_iBufAvail;
|
||||
}
|
||||
|
||||
if( done )
|
||||
break;
|
||||
|
||||
/* We need more data. */
|
||||
m_pBuf = m_Buffer;
|
||||
|
||||
const int size = FillBuf();
|
||||
|
||||
/* If we've read data already, then don't mark EOF yet. Wait until the
|
||||
* next time we're called. */
|
||||
if( size == 0 && !GotData )
|
||||
{
|
||||
m_bEOF = true;
|
||||
return 0;
|
||||
}
|
||||
if( size == -1 )
|
||||
return -1; // error
|
||||
if( size == 0 )
|
||||
break; // EOF or error
|
||||
}
|
||||
return GotData? 1:0;
|
||||
}
|
||||
|
||||
// Always use "\r\n". Even though the program may be running on Unix, the
|
||||
// files written to a memory card are likely to be edited using Windows.
|
||||
//#if defined(_WIN32)
|
||||
#define NEWLINE "\r\n"
|
||||
//#else
|
||||
//#define NEWLINE "\n"
|
||||
//#endif
|
||||
|
||||
int RageFileObj::PutLine( const CString &str )
|
||||
{
|
||||
if( Write(str) == -1 )
|
||||
return -1;
|
||||
return Write( CString(NEWLINE) );
|
||||
}
|
||||
|
||||
/* Fill the internal buffer. This never marks EOF, since this is an internal, hidden
|
||||
* read; EOF should only be set as a result of a real read. (That is, disabling buffering
|
||||
* shouldn't cause the results of AtEOF to change.) */
|
||||
int RageFileObj::FillBuf()
|
||||
{
|
||||
/* The buffer starts at m_Buffer; any data in it starts at m_pBuf; space between
|
||||
* the two is old data that we've read. (Don't mangle that data; we can use it
|
||||
* for seeking backwards.) */
|
||||
const int iBufAvail = sizeof(m_Buffer) - (m_pBuf-m_Buffer) - m_iBufAvail;
|
||||
ASSERT_M( iBufAvail >= 0, ssprintf("%p, %p, %i", m_pBuf, m_Buffer, (int) sizeof(m_Buffer) ) );
|
||||
const int size = this->ReadInternal( m_pBuf+m_iBufAvail, iBufAvail );
|
||||
|
||||
if( size > 0 )
|
||||
m_iBufAvail += size;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
void RageFileObj::ResetBuf()
|
||||
{
|
||||
m_iBufAvail = 0;
|
||||
m_pBuf = m_Buffer;
|
||||
}
|
||||
|
||||
/*
|
||||
* Copyright (c) 2003-2004 Glenn Maynard
|
||||
* All rights reserved.
|
||||
|
||||
@@ -31,34 +31,35 @@ protected:
|
||||
FilenameDB *FDB;
|
||||
};
|
||||
|
||||
class RageFileObj
|
||||
class RageFileObj: public RageBasicFile
|
||||
{
|
||||
public:
|
||||
RageFileObj();
|
||||
virtual ~RageFileObj() { }
|
||||
|
||||
virtual CString GetError() const { return m_sError; }
|
||||
CString GetError() const { return m_sError; }
|
||||
void ClearError() { SetError(""); }
|
||||
|
||||
/* Seek to the given absolute offset. Return to the position actually
|
||||
* seeked to; if the position given was beyond the end of the file, the
|
||||
* return value will be the size of the file. */
|
||||
bool AtEOF() const { return m_bEOF; }
|
||||
|
||||
int Seek( int iOffset );
|
||||
int Seek( int offset, int whence );
|
||||
int Tell() const { return m_iFilePos; }
|
||||
|
||||
/* Read at most iSize bytes into pBuf. Return the number of bytes read,
|
||||
* 0 on end of stream, or -1 on error. Note that reading less than iSize
|
||||
* does not necessarily mean that the end of the stream has been reached;
|
||||
* keep reading until 0 is returned. */
|
||||
int Read( void *pBuffer, size_t iBytes );
|
||||
int Read( CString &buffer, int bytes = -1 );
|
||||
int Read( void *buffer, size_t bytes, int nmemb );
|
||||
|
||||
/* Write iSize bytes of data from pBuf. Return 0 on success, -1 on error. */
|
||||
int Write( const void *pBuffer, size_t iBytes );
|
||||
int Write( const CString &sString ) { return Write( sString.data(), sString.size() ); }
|
||||
int Write( const void *buffer, size_t bytes, int nmemb );
|
||||
|
||||
/* Due to buffering, writing may not happen by the end of a Write() call, so not
|
||||
* all errors may be returned by it. Data will be flushed when the stream (or its
|
||||
* underlying object) is destroyed, but errors can no longer be returned. Call
|
||||
* Flush() to flush pending data, in order to check for errors. */
|
||||
int Flush();
|
||||
|
||||
virtual int GetFileSize() = 0;
|
||||
int GetLine( CString &out );
|
||||
int PutLine( const CString &str );
|
||||
|
||||
virtual int GetFileSize() const = 0;
|
||||
virtual CString GetDisplayPath() const { return ""; }
|
||||
virtual RageFileObj *Copy() const { FAIL_M( "Copying unimplemented" ); }
|
||||
|
||||
@@ -68,8 +69,20 @@ protected:
|
||||
virtual int WriteInternal( const void *pBuffer, size_t iBytes ) = 0;
|
||||
virtual int FlushInternal() { return 0; }
|
||||
|
||||
virtual void SetError( const CString &sError ) { m_sError = sError; }
|
||||
void SetError( const CString &sError ) { m_sError = sError; }
|
||||
CString m_sError;
|
||||
|
||||
private:
|
||||
int FillBuf();
|
||||
void ResetBuf();
|
||||
|
||||
bool m_bEOF;
|
||||
int m_iFilePos;
|
||||
|
||||
enum { BSIZE = 1024 };
|
||||
char m_Buffer[BSIZE];
|
||||
char *m_pBuf;
|
||||
int m_iBufAvail;
|
||||
};
|
||||
|
||||
/* This is used to register the driver, so RageFileManager can see it. */
|
||||
|
||||
@@ -49,7 +49,7 @@ public:
|
||||
virtual int SeekInternal( int offset );
|
||||
virtual RageFileObj *Copy() const;
|
||||
virtual CString GetDisplayPath() const { return path; }
|
||||
virtual int GetFileSize();
|
||||
virtual int GetFileSize() const;
|
||||
};
|
||||
|
||||
|
||||
@@ -365,7 +365,7 @@ int RageFileObjDirect::SeekInternal( int offset )
|
||||
return lseek( fd, offset, SEEK_SET );
|
||||
}
|
||||
|
||||
int RageFileObjDirect::GetFileSize()
|
||||
int RageFileObjDirect::GetFileSize() const
|
||||
{
|
||||
const int OldPos = lseek( fd, 0, SEEK_CUR );
|
||||
ASSERT_M( OldPos != -1, strerror(errno) );
|
||||
|
||||
@@ -73,7 +73,7 @@ public:
|
||||
return m_iFilePos;
|
||||
}
|
||||
|
||||
int GetFileSize()
|
||||
int GetFileSize() const
|
||||
{
|
||||
LockMut(m_pFile->m_Mutex);
|
||||
return m_pFile->m_sBuf.size();
|
||||
|
||||
@@ -84,7 +84,7 @@ public:
|
||||
int ReadInternal( void *pBuffer, size_t iBytes );
|
||||
int WriteInternal( const void *pBuffer, size_t iBytes ) { SetError( "Not implemented" ); return -1; }
|
||||
int SeekInternal( int iOffset );
|
||||
int GetFileSize() { return info.uncompr_size; }
|
||||
int GetFileSize() const { return info.uncompr_size; }
|
||||
RageFileObj *Copy() const
|
||||
{
|
||||
RageException::Throw( "Loading ZIPs from deflated ZIPs is currently disabled; see RageFileObjZipDeflated" );
|
||||
@@ -106,7 +106,7 @@ public:
|
||||
int WriteInternal( const void *pBuffer, size_t iBytes ) { SetError( "Not implemented" ); return -1; }
|
||||
|
||||
int SeekInternal( int iOffset );
|
||||
int GetFileSize() { return info.uncompr_size; }
|
||||
int GetFileSize() const { return info.uncompr_size; }
|
||||
|
||||
RageFileObj *Copy() const
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user