/***************************************************************************
                           cfile.cpp  -  description
                             -------------------
    begin                : Wed Apr 3 2003
    copyright            : (C) 2003-2004 by Mathias Küster
    email                : mathen@users.berlios.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "cfile.h"

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

#ifndef WIN32
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#endif

#include "cbytearray.h"

#define FILE_BUFFER_SIZE	(100*1024)

/** */
CFile::CFile()
{
	m_nFD     = -1;
	m_pBuffer = 0;
}

/** */
CFile::~CFile()
{
	Close();

	delete m_pBuffer;
	m_pBuffer = 0;
}

/** */
bool CFile::Open( CString filename, int mode, int acc )
{
	bool res = false;
	int m = 0, a = 0;

	if ( (m_nFD != -1) || filename.IsEmpty() )
	{
		return res;
	}

	m_nMode      = mode;
	m_nBufferPos = 0;

#ifdef WIN32
	if ( (mode & IO_RAW) != 0 )
		m |= _O_BINARY;
	if ( (mode & IO_READONLY) != 0 )
		m |= _O_RDONLY;
	if ( (mode & IO_WRITEONLY) != 0 )
		m |= _O_WRONLY;
	if ( (mode & IO_READWRITE) != 0 )
		m |= _O_RDWR;
	if ( (mode & IO_APPEND) != 0 )
		m |= _O_APPEND;
	if ( (mode & IO_TRUNCATE) != 0 )
		m |= _O_TRUNC;
	if ( (mode & IO_CREAT) != 0 )
		m |= _O_CREAT;
#else
	if ( (mode & IO_RAW) != 0 )
		m |= 0;
	if ( (mode & IO_READONLY) != 0 )
		m |= O_RDONLY;
	if ( (mode & IO_WRITEONLY) != 0 )
		m |= O_WRONLY;
	if ( (mode & IO_READWRITE) != 0 )
		m |= O_RDWR;
	if ( (mode & IO_APPEND) != 0 )
		m |= O_APPEND;
	if ( (mode & IO_TRUNCATE) != 0 )
		m |= O_TRUNC;
	if ( (mode & IO_CREAT) != 0 )
		m |= O_CREAT;
#endif

#ifdef WIN32
	if ( (acc & MO_IRWXU) != 0 )
		a |= _S_IREAD | _S_IWRITE;
	if ( (acc & MO_IRUSR) != 0 )
		a |= _S_IREAD;
	if ( (acc & MO_IWUSR) != 0 )
		a |= _S_IWRITE;
	if ( (acc & MO_IRWXG) != 0 )
		a |= _S_IREAD | _S_IWRITE;
	if ( (acc & MO_IRGRP) != 0 )
		a |= _S_IREAD;
	if ( (acc & MO_IWGRP) != 0 )
		a |= _S_IWRITE;
	if ( (acc & MO_IRWXO) != 0 )
		a |= _S_IREAD | _S_IWRITE;
	if ( (acc & MO_IRGRP) != 0 )
		a |= _S_IREAD;
	if ( (acc & MO_IWOTH) != 0 )
		a |= _S_IWRITE;
#else
	if ( (acc & MO_IRWXU) == MO_IRWXU )
		a |= S_IRWXU;
	if ( (acc & MO_IRUSR) == MO_IRUSR )
		a |= S_IRUSR;
	if ( (acc & MO_IWUSR) == MO_IWUSR )
		a |= S_IWUSR;
	if ( (acc & MO_IXUSR) == MO_IXUSR )
		a |= S_IXUSR;
	if ( (acc & MO_IRWXG) == MO_IRWXG )
		a |= S_IRWXG;
	if ( (acc & MO_IRGRP) == MO_IRGRP )
		a |= S_IRGRP;
	if ( (acc & MO_IWGRP) == MO_IWGRP )
		a |= S_IWGRP;
	if ( (acc & MO_IXGRP) == MO_IXGRP )
		a |= S_IXGRP;
	if ( (acc & MO_IRWXO) == MO_IRWXO )
		a |= S_IRWXO;
	if ( (acc & MO_IRGRP) == MO_IRGRP )
		a |= S_IROTH;
	if ( (acc & MO_IWOTH) == MO_IWOTH )
		a |= S_IWOTH;
	if ( (acc & MO_IXOTH) == MO_IXOTH )
		a |= S_IXOTH;
#endif

#ifdef WIN32
	m_nFD = _open( filename.Data(), m, a );
#else
	m_nFD = open( filename.Data(), m, a );
#endif

	if ( m_nFD != -1 )
	{
		res = true;

		// create write buffer
		if ( (mode & IO_WRITEONLY) != 0 )
		{
			m_pBuffer = new CByteArray(FILE_BUFFER_SIZE);
		}
	}

	return res;
}

/** */
bool CFile::Close()
{
	bool ok = false;
	if ( m_nFD != -1 )
	{
		// flush write buffer
		if ( m_nBufferPos != 0 )
		{
			Flush();
		}
#ifdef WIN32
		if ( _close(m_nFD) == 0 )
#else
		if ( close(m_nFD) == 0 )
#endif
		{
			ok = true;
		}
		else
		{
			perror("CFile::Close");
		}

		m_nFD        = -1;
		m_nBufferPos = 0;

		delete m_pBuffer;
		m_pBuffer = 0;
	}
	
	return ok;
}

/** */
long CFile::Flush()
{
	long i = 0;

	if ( (m_nFD != -1) &&
	     ((m_nMode & IO_WRITEONLY) != 0) &&
	     (m_nBufferPos != 0) )
	{
#ifdef WIN32
		i = _write( m_nFD, m_pBuffer->Data(), m_nBufferPos );
#else
		i = write( m_nFD, m_pBuffer->Data(), m_nBufferPos );
#endif
		if ( i == -1 )
		{
			perror("CFile::Flush");
		}
		else if ( i < m_nBufferPos )
		{
			// not all data flushed, fix buffer
			printf("CFile::Flush: write %ld : %d Bytes\n",i,m_nBufferPos);

			if ( i > 0 )
			{
				memcpy(m_pBuffer->Data(),m_pBuffer->Data()+i,m_nBufferPos-i);
				m_nBufferPos -= i;
			}

			i = -1;
		}
		else
		{
			m_nBufferPos = 0;
		}
	}

	return i;
}

/** */
long CFile::Write( const char * buf, long count )
{
	long i = 0;

	if ( (m_nFD == -1) || (count <= 0) || (!buf) )
	{
		return -1;
	}

	if ( (m_nMode & IO_WRITEONLY) != 0 )
	{
		if ( count >= FILE_BUFFER_SIZE )
		{
			i = Flush();
		}
		else if ( (count+m_nBufferPos) > FILE_BUFFER_SIZE )
		{
			i = Flush();

			if ( i != -1 )
			{
				m_nBufferPos = count;
				memcpy(m_pBuffer->Data(),buf,count);

				i = count;
			}
		}
		else
		{
			memcpy(m_pBuffer->Data()+m_nBufferPos,buf,count);
			m_nBufferPos += count;

			i = count;
		}
	}

	// check if flush failed
	if ( i != -1 )
	{
		if ( m_nBufferPos == 0 )
		{
#ifdef WIN32
			i = _write( m_nFD, buf, count );
#else
			i = write( m_nFD, buf, count );
#endif
		}
	}

	return i;
}

/** */
long CFile::Read( char * buf, long count )
{
	long i = -1;

	if ( (m_nFD == -1) || (count <= 0) || (!buf))
	{
		return i;
	}

#ifdef WIN32
	i = _read( m_nFD, buf, count );
#else
	i = read( m_nFD, buf, count );
#endif

	return i;
}

/** */
bool CFile::Seek( int64_t offset, int how )
{
	bool res = false;

	if ( m_nFD == -1 )
	{
		return res;
	}

	if ( Flush() == -1 )
	{
		return res;
	}

#ifdef WIN32
	if ( _lseeki64( m_nFD, offset, how ) == -1 )
#else
	if ( lseek( m_nFD, offset, how ) == (off_t) -1 )
#endif
	{
		res = false;
	}
	else
	{
		res = true;
	}

	return res;
}

/** */
bool CFile::Rename( const CString & from, const CString & to )
{
	bool res = false;
	
	if ( from.IsEmpty() || to.IsEmpty() )
	{
		return res;
	}
	
	if ( rename(from.Data(),to.Data()) == 0 )
	{
		res = true;
	}
	else
	{
		perror("CFile::Rename");
	}
	
	return res;
}

/** */
bool CFile::Remove( const CString & s )
{
	bool res = false;
	
	if ( s.IsEmpty() )
	{
		return res;
	}
	
	if ( remove(s.Data()) == 0 )
	{
		res = true;
	}
	else
	{
		perror("CFile::Remove");
	}
	
	return res;
}

/** */
bool CFile::UnLink( const CString & absfile )
{
	bool res = false;
	
	if ( absfile.IsEmpty() )
	{
		return res;
	}
	
#ifdef WIN32
	if ( _unlink( absfile.Data() ) == 0 )
#else
	if ( unlink( absfile.Data() ) == 0 )
#endif
	{
		res = true;
	}
	
	return res;
}

/** */
bool CFile::Copy( const CString & absfrom, const CString & absto )
{
	int from_fd, to_fd;
	ssize_t bytesread;
	char * buffer;
	
#ifdef WIN32
	from_fd = open( absfrom.Data(), _O_RDONLY | _O_BINARY, 0 );
#else
	from_fd = open( absfrom.Data(), O_RDONLY , 0 );
#endif
	
	if ( from_fd < 0 )
	{
		printf("CFile::Copy: open for read '%s' failed\n",absfrom.Data());
		return false;
	}
	
#ifdef WIN32
	to_fd = open( absto.Data(), _O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY, _S_IREAD | _S_IWRITE );
#else
	UnLink( absto );
	to_fd = open( absto.Data(), O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, S_IRWXU | S_IRWXG | S_IRWXO );
#endif
	
	if ( to_fd < 0 )
	{
		printf("CFile::Copy: open for write '%s' failed\n",absto.Data());
		return false;
	}
	
#ifdef WIN32
	const long blocksize = 65536;
#else
	struct stat statbuf;
	blksize_t blocksize;
	if ( fstat( to_fd, &statbuf ) == 0 )
	{
		blocksize = statbuf.st_blksize;
		/* less necessary */
		if ( (fstat(from_fd,&statbuf) == 0) && (statbuf.st_blksize > blocksize) )
		{
			blocksize = statbuf.st_blksize;
		}
	}
	else
	{
		blocksize = 65536;
	}
#endif
	
	buffer = (char*) malloc( blocksize );
	
	if ( buffer == 0 )
	{
		printf("CFile::Copy: malloc failed\n");
		return false;
	}
	
	while ( (bytesread = read(from_fd,buffer,blocksize)) > 0 )
	{
		if ( write(to_fd,buffer,bytesread) != bytesread )
		{
			printf("CFile::Copy: write failed\n");
#ifdef WIN32
			if ( _close(from_fd) != 0 )
#else
			if ( close(from_fd) != 0 )
#endif
			{
				printf("CFile::Copy: close '%s' also failed\n",absfrom.Data());
			}
#ifdef WIN32
			if ( _close(to_fd) != 0 )
#else
			if ( close(to_fd) != 0 )
#endif
			{
				printf("CFile::Copy: close '%s' also failed\n",absto.Data());
			}
			UnLink( absto );
			free(buffer);
			return false;
		}
	}
	
	free(buffer);
	
	bool res = true;
	
#ifdef WIN32
	if ( _close(to_fd) != 0 )
#else
	if ( close(to_fd) != 0 )
#endif
	{
		printf("CFile::Copy: close '%s' failed\n",absto.Data());
		UnLink( absto );
		res = false;
	}
	
#ifdef WIN32
	if ( _close(from_fd) != 0 )
#else
	if ( close(from_fd) != 0 )
#endif
	{
		printf("CFile::Copy: close '%s' failed\n",absfrom.Data());
		/* since target was sucessfully created true can be returned */
	}
	
	return res;
}

/** */
bool CFile::OpenTemp( CString & filename )
{
	bool ok = false;
	
	if ( (m_nFD != -1) || filename.IsEmpty() )
	{
		return ok;
	}
	
	m_nBufferPos = 0;
	m_nMode = IO_RAW | IO_WRITEONLY | IO_CREAT;
	
	CString tempname = filename;
	tempname += ".XXXXXX";

#ifdef WIN32
	char * res = _mktemp( tempname.Data() );
	
	if ( res )
	{
		ok = Open( tempname, m_nMode );
		if ( ok )
		{
			filename = tempname;
		}
	}
	else
	{
		ok = false;
	}
#else
	m_nFD = mkstemp( tempname.Data() );
	
	if ( m_nFD != -1 )
	{
		ok = true;
		m_pBuffer = new CByteArray(FILE_BUFFER_SIZE);
		filename = tempname;
	}
#endif
	return ok;
}
