FileManager - easy and fast file i/o

Post those lines of code you feel like sharing or find what you require for your project here; or simply use them as tutorials.
Post Reply
vi-wer
Posts: 93
Joined: Sun May 20, 2007 7:15 pm
Location: Germany
Contact:

FileManager - easy and fast file i/o

Post by vi-wer »

Here's a file i/o class I made to save and load my scene files. It's easy to use and based on the chunck file format that is used in .3ds and other 3D model files.
First you read/write a header and then the data. Only the data that was writen to a chunck will be read, not more.
A chunck can't be written into another one, this for the code has to be changed.

Well, look that example:

Code: Select all

int n =2;
CFileManager fmgr("file.dat",WRITE);
fmgr.begin( /*header type here*/ );
fmgr.writeString( "Some text" );
fmgr.write( &n,sizeof(int));
fmgr.end();

//-------

int n;
CFileManager fmgr("file.dat",READ);
if( /*header type*/ == fmgr.readHeader())
{
  std::string text;
  fmgr.readString( text );
  fmgr.read( &n,sizeof(int));
}
The file will be closed if the destructor was called. Create the filemanager with new would be a better choice probably, because it can be deleted when not needed.

CFileManager.h

Code: Select all

#ifndef CRFILEMANAGER_H_INCLUDED
#define CRFILEMANAGER_H_INCLUDED



#include <stdio.h>
#include <string>



#define READ    true
#define WRITE   false


//Filename class
class CFilename
{
    public:
        CFilename();
        CFilename( const char* name);
        virtual ~CFilename();

        std::string m_sFilename;

        const char* getName();
        const char* getFullname();
        const char* getRelativeName( const char *actualDir );
        const char* getPath();
        const char* getFullPath();
};

//File manager
class CRFileManager
{
    public:
        CRFileManager( const char *pszFilename,bool bRead);
        ~CRFileManager();

        // write funstions
        void begin( int headerType );
        void write( const void *data, int size );
        void writeString( const char *data );
        void writeString( int data );
        void writeBoolAsString( bool data );
        void writeString( float data );
        void end();
        bool isEndOfFile();

        // read functions
        int readHeader();
        void read( void *data, int size );
        void readString( std::string& data );
        void skip();

        const char* getFilename();

        bool isOK();

    private:
        int    m_sizePos;
        int    m_size;
        int    m_sizeAlreadyRead;
        int    m_chunckEnd;
        bool        m_bOpenForRead;
        bool        m_bWriteEnable;

        std::string m_sFileName;

        FILE *m_writeFile;
        FILE *m_readFile;
};

#endif // CRFILEMANAGER_H_INCLUDED
CFileManager.cpp:

Code: Select all

#include "CRFileManager.h"


CFilename::CFilename()
{
    m_sFilename = "";
}

CFilename::CFilename( const char* name)
{
    m_sFilename = name;
}

CFilename::~CFilename()
{
}

const char* CFilename::getName()
{
    std::string sName = m_sFilename;

    std::string::size_type pos = m_sFilename.rfind('.');

    // remove extension
    if ( pos != std::string::npos )
    {
        // '.' gefunden
        // Teilzeichenkette exkl. zweitem Argument (hier ohne '.')
        sName = m_sFilename.substr(0,pos);
    }

    pos = sName.rfind('\\');

    // remove path
    if ( pos == std::string::npos ) // not found
    {
        pos = sName.rfind('/');

        if ( pos != std::string::npos ) // not found
        {
            sName = sName.substr(pos+1);
        }
    }
    else
    {
        // '\' gefunden
        // Teilzeichenkette exkl. zweitem Argument (hier ohne '.')
        sName = sName.substr(pos+1);
    }

    return sName.c_str();
}

const char* CFilename::getFullname()
{
    std::string sName = m_sFilename;

    std::string::size_type pos;

    pos = m_sFilename.rfind('\\');

    // remove path
    if ( pos == std::string::npos ) // not found
    {
        pos = m_sFilename.rfind('/');

        if ( pos != std::string::npos ) // not found
        {
            sName = m_sFilename.substr(pos+1);
        }
    }
    else
    {
        // '\' gefunden
        sName = m_sFilename.substr(pos+1);
    }

    return sName.c_str();
}

const char* CFilename::getPath()
{
    std::string sPath = m_sFilename;

    std::string::size_type pos;

    pos = m_sFilename.rfind('\\');

    // remove name
    if ( pos == std::string::npos ) // not found
    {
        pos = m_sFilename.rfind('/');

        if ( pos != std::string::npos ) // not found
        {
            sPath = m_sFilename.substr(0,pos);
        }
    }
    else
    {
        // '\' gefunden
        // Teilzeichenkette exkl. zweitem Argument (hier ohne '.')
        sPath = m_sFilename.substr(0,pos);
    }

    return sPath.c_str();
}

const char* CFilename::getFullPath()
{
    return m_sFilename.c_str();
}

const char* CFilename::getRelativeName( const char *actualDir )
{
    CFilename workDir(actualDir);
    std::string relName = m_sFilename;
    workDir.m_sFilename = workDir.getPath();

    //Compare
    int i=0;

    while( relName.compare(0,i,workDir.m_sFilename.substr(0,i)) == 0)
    {
        i++;
    }

    if(i==0)
        return m_sFilename.c_str();

    // Erase first part
    i--;
    workDir.m_sFilename.erase(0,i); // = path/path/
    relName.erase(0,i);     //  = path/name.ext

    // return name
    if( workDir.m_sFilename.empty() )
        return relName.c_str();

    //Get depth
    std::string::size_type pos = 0;
    for( i=0; i=i;i++)
    {
        pos = workDir.m_sFilename.find( '/' , pos);

        if( std::string::npos == pos )
            break;
        else
            pos += 1;

    }

    // add '../' to relName
    for( int t=0;t<i;t++ )
    {
        relName = "../" + relName;
    }

    return relName.c_str();
}

CRFileManager::CRFileManager( const char *pszFilename, bool bRead)
{
    m_sizePos = 0;
    m_size = 0;
    m_sizeAlreadyRead = 0;
    m_chunckEnd = 0;
    m_bOpenForRead = bRead;
    m_bWriteEnable = false;

    m_writeFile = 0;
    m_readFile = 0;

    m_sFileName = pszFilename;

    if( m_bOpenForRead )        //Create read file
        m_readFile = fopen(pszFilename,"rb");
    else
        m_writeFile = fopen(pszFilename,"wb");

}

CRFileManager::~CRFileManager()
{
    if( m_readFile )
        fclose( m_readFile );

    if( m_writeFile )
        fclose( m_writeFile );
}

bool CRFileManager::isOK()
{
    if( !m_writeFile && !m_bOpenForRead )
        return false;

    if( !m_readFile && m_bOpenForRead )
        return false;

    return true;
}

const char* CRFileManager::getFilename()
{
    return m_sFileName.c_str();
}

void CRFileManager::begin( int headerType )
{
    if( m_writeFile && !m_bOpenForRead && !m_bWriteEnable)
    {
        m_size = 0;

        // Write header type
        fwrite( &headerType, sizeof(int), 1 , m_writeFile );
        m_sizePos = ftell( m_writeFile ); 
        fwrite( &m_size, sizeof(int), 1 , m_writeFile );

        m_bWriteEnable = true;
    }
}

void CRFileManager::write( const void *data, int size )
{
    if( m_writeFile && !m_bOpenForRead && m_bWriteEnable)
    {
        //Write data
        m_size += size;
        fwrite( data, size, 1 , m_writeFile );        
    }
}

void CRFileManager::writeString( const char *data )
{
    if( m_writeFile && !m_bOpenForRead && m_bWriteEnable)
    {
        m_size += strlen(data)+1;
        fwrite( data, strlen(data)+1, 1 , m_writeFile );        
    }
}

void CRFileManager::writeBoolAsString( bool data )
{
    if( data )
        writeString( "true" );
    else
        writeString( "false" );
}


void CRFileManager::writeString( float data )
{
    char szbuffer[32];
    _gcvt(data,16,szbuffer);
    writeString( szbuffer );
}

void CRFileManager::writeString( int data )
{
    char szbuffer[32];
    itoa( data, szbuffer, 10 );
    writeString( szbuffer );
}

void CRFileManager::end()
{
    if( m_writeFile && !m_bOpenForRead && m_bWriteEnable)
    {        
        m_chunckEnd = ftell( m_writeFile );
        //Write data size
        
        fseek( m_writeFile, m_sizePos,SEEK_SET );
        fwrite( &m_size, sizeof(int),1,m_writeFile );
     
        //Get back to the end
        fseek( m_writeFile, m_chunckEnd,SEEK_SET );

        m_bWriteEnable = false;
    }
}

int CRFileManager::readHeader()
{
    if( !m_readFile && !m_bOpenForRead)
        return -1;

    skip();

    int header;
    fread( &header, sizeof(int), 1, m_readFile );
    fread( &m_size, sizeof(int), 1, m_readFile );
    
    return header;
}

void CRFileManager::skip()
{
    fseek( m_readFile, m_size, SEEK_CUR);
    
    m_size = 0;
}

void CRFileManager::readString( std::string& data )
{
    if( (!m_readFile && !m_bOpenForRead) || m_size == 0 )
        return;

    data = "";

    char c = '\r';

    while( c != '\0' )
    {
        fread( &c, sizeof(char) ,1 ,m_readFile );
        data += c;

        m_size--;
    }
}

void CRFileManager::read( void *data, int size )
{
    if( (!m_readFile && !m_bOpenForRead) || m_size == 0 )
        return;

    fread( data, size , 1, m_readFile );
    
    m_size -= size;
}


bool CRFileManager::isEndOfFile()
{
    if( m_readFile )
        return feof( m_readFile );

    return true;
}
hybrid
Admin
Posts: 14143
Joined: Wed Apr 19, 2006 9:20 pm
Location: Oldenburg(Oldb), Germany
Contact:

Post by hybrid »

Besides the fact that FileManager seems a little misleading Irrlicht already has lots of I/O functions which would make your code much more portable and also directed towards the file format you want to support. Take a look at the IFileSystem and IReadFile/IWriteFile interfaces. This would, e.g., also allow to load this file format from zip files.
It might be also a little type unsafe - just as 3ds etc. Assuming an int will follow will easily lead to completely wrong data. At least you did not choose binary number representation, which would have required endianess conversions etc. But is this format really simpler or faster than XML?
An idea would be to define sections like in IFF. You simply define the section you'll write into and it's either overwritten or appended. Right now the section header seems just like a mnemonic to reassure that the expected data will follow (or as the separator).
vi-wer
Posts: 93
Joined: Sun May 20, 2007 7:15 pm
Location: Germany
Contact:

Post by vi-wer »

In the previous version I used IReadFile/IWriteFile, don't know why I changed it. Maybe to use it in other apps without Irrlicht.

But it should be faster than XML because the data is read/written directly, while XML files has to be parsed ( ok, it does not take much more time ).
An anvantage is probably the size of the file. Imagine a mesh model with some thousands of vertices that is saved as .xml. Each position would be written as string and would need a lot of bytes, while here it's written as 3 floats binary data = 12Bytes. ( well, you do not really need this class to write that kind of data, but isn't it a little bit easier? ).

It's not perfect, but everyone can change it for his own purpose.
hybrid
Admin
Posts: 14143
Joined: Wed Apr 19, 2006 9:20 pm
Location: Oldenburg(Oldb), Germany
Contact:

Post by hybrid »

Oh, so this strance _gcvt does some binary transformation? that's bad. Are you even sure that no null byte will appear? Also note that reading each float separately may be much slower than reading all floats at once and parsing them afterwards (i.e. from strings in memory). This is due to inefficient file locking on each access.
Post Reply