diff --git a/stepmania/src/RageFileBasic.cpp b/stepmania/src/RageFileBasic.cpp new file mode 100644 index 0000000000..b6236bfebd --- /dev/null +++ b/stepmania/src/RageFileBasic.cpp @@ -0,0 +1,272 @@ +#include "global.h" +#include "RageFileBasic.h" +#include "RageUtil.h" + +RageFileObj::RageFileObj() +{ + ResetBuf(); + + m_iBufAvail = 0; + m_bEOF = false; + m_iFilePos = 0; +} + +int RageFileObj::Seek( int 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 ) +{ + 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 ) +{ + 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; +} + diff --git a/stepmania/src/RageFileBasic.h b/stepmania/src/RageFileBasic.h new file mode 100644 index 0000000000..20d1b3a8aa --- /dev/null +++ b/stepmania/src/RageFileBasic.h @@ -0,0 +1,108 @@ +#ifndef RAGE_FILE_BASIC_H +#define RAGE_FILE_BASIC_H + +/* 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 RageFileBasic +{ +public: + virtual ~RageFileBasic() { } + + 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; + + /* This returns a descriptive path for the file, or "". */ + virtual CString GetDisplayPath() const { return ""; } + + virtual RageFileBasic *Copy() const = 0; + + virtual int GetLine( CString &out ) = 0; + virtual int PutLine( const CString &str ) = 0; + + virtual int GetFileSize() const = 0; +}; + +class RageFileObj: public RageFileBasic +{ +public: + RageFileObj(); + virtual ~RageFileObj() { } + + virtual CString GetError() const { return m_sError; } + virtual void ClearError() { SetError(""); } + + bool AtEOF() const { return m_bEOF; } + + int Seek( int iOffset ); + int Seek( int offset, int whence ); + int Tell() const { return m_iFilePos; } + + int Read( void *pBuffer, size_t iBytes ); + int Read( CString &buffer, int bytes = -1 ); + int Read( void *buffer, size_t bytes, int nmemb ); + + 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 ); + + int Flush(); + + int GetLine( CString &out ); + int PutLine( const CString &str ); + + virtual int GetFileSize() const = 0; + virtual CString GetDisplayPath() const { return ""; } + virtual RageFileBasic *Copy() const { FAIL_M( "Copying unimplemented" ); } + +protected: + virtual int SeekInternal( int iOffset ) { FAIL_M( "Seeking unimplemented" ); } + virtual int ReadInternal( void *pBuffer, size_t iBytes ) = 0; + virtual int WriteInternal( const void *pBuffer, size_t iBytes ) = 0; + virtual int FlushInternal() { return 0; } + + 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; +}; + +#endif +