/***************************************************************************

    supportware
    Copyright (C) 2001-2008 by c.holzheuer
    c.holzheuer@sourceworx.org

    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 <ddwebbuffer.h>
#include <ddwebconnection.h>
#include <ddiommap.h>
#include <ddthreadmutex.h>

#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/sendfile.h>
#include <netdb.h>
#include <string.h>


using namespace std;

ddwebbuffer::ddwebbuffer()
: _fd( -1 ), _filefd( -1 ), _nextmode( ddwebconnection::write_buffer ), _offset( 0 ),
  _filesize( 0 ), _corked( false )
{
	set_state_flags( ddwebconnection::write_header );
	init_buffer( DDBUFFERINBUFSIZE );
	_outhead.init_buffer( DDBUFFERINBUFSIZE );
	_outbody.init_buffer( DDBUFFEROUTBUFSIZE  );	
}


ddwebbuffer::~ddwebbuffer()
{
}


void ddwebbuffer::set_write_mode( int mode )
{
	_nextmode = mode;
}


size_t ddwebbuffer::set_path( ddiourl& path )
{
	// ioinfo schmeisst, wenn das file nicht existiert.
	try
	{
		_filefd = ::open( path.get_full_path().c_str(), O_RDONLY );
		if( _filefd < 0 )
			return 0;
				
		set_file_size( path.get_file_size() );
		_offset = 0;
		_name = path.get_full_path();
		
		// aus lighttp: 
		//if (-1 == posix_fadvise(c->file.fd, 0, 0, POSIX_FADV_SEQUENTIAL)) {
		/*
		// aus fnord
		if (len<4096) {	// for small files, sendfile is actually slower 
    char tmp[4096];
    if (rangestart) lseek(fd,rangestart,SEEK_SET);
    read(fd,tmp,len);	
    */

	}
	catch( exception& error )
	{
		log_error( error.what() );
		_filesize = 0;
	}	
	return _filesize;

}


void ddwebbuffer::set_file_size( size_t filesize )
{
	_filesize = filesize;
	_max = _filesize;
}
	
	
int ddwebbuffer::connect( const ddstring& host )
{

	struct hostent *he;
	struct sockaddr_in their_addr; // connector's address information 

	// get the host info 
	if( ( he = ::gethostbyname( host.c_str() ) ) == NULL )
	{ 
	    perror("gethostbyname");
	    return -1;
	}

	if( ( _fd = socket( AF_INET, SOCK_STREAM, 0 ) ) < 0 )
	{
	    perror("socket");
	    return -1;
	}

	their_addr.sin_family = AF_INET;    // host byte order 
	their_addr.sin_port = htons( 80 );  // short, network byte order 
	their_addr.sin_addr = *((struct in_addr *)he->h_addr);
	::memset(&(their_addr.sin_zero), '\0', 8);  // zero the rest of the struct 

	if( ::connect( _fd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) < 0 )
	{
	    perror("connect");
	    return -1;
	}
	
	return 1;

}

int ddwebbuffer::accept( int masterfd )
{
	struct sockaddr_in sa_remote, sa_local;
	socklen_t sin_size = sizeof sa_remote;
	
	_fd = ::accept( masterfd, (struct sockaddr *) &sa_remote, &sin_size  );
	if( _fd < 0 )
	{
		if( errno == EAGAIN )
			return 0;
		return -1;
	}

	::fcntl( _fd , F_SETFD, FD_CLOEXEC );
	::fcntl( _fd , F_SETFL, O_NONBLOCK );
	
	sin_size = sizeof sa_local;
	if( ::getsockname( _fd , (struct sockaddr *) &sa_local, &sin_size ) < 0 )
	{
		::close( _fd  );
		return -1;
	}
	// neu
	_hostip = ::inet_ntoa( sa_remote.sin_addr );
	
	return 1;
	
}
	

int ddwebbuffer::receive()
{
	int rs = max_input();
	
	try
	{
		if( rs <= 0 )
			rs = grow_buffer( DDBUFFERINBUFSIZE );
	}
	catch( exception& error )
	{
		// darf eigentlich nicht passieren: out of memory in grow...
		log_error( ddstring( "ddwebbuffer::receive: input buffer overflow") + error.what() );		
		return -1;
	}
			
	// we can read
	int	bytes = ::recv( _fd, input(), rs, 0 );	
	if( bytes > 0 )
	{
		move_input( bytes );
		return bytes;
	}
	
	// ab hier: bytes < 0
	if( errno == EAGAIN )
		return 0;
	
	return -1;	
}


int ddwebbuffer::send()
{
	int result, bytes;
	
x:switch( _flags )
	{
		
		case ddwebconnection::write_header :
			
			// verkorken
			cork( true );
			// schreiben
	 		result = send_buffer( _outhead, _nextmode, MSG_MORE );
			// fehler oder unvollständig
			if( result <= 0 )
				return result;
			// nochmal reinspringen um body zu senden
			// (mein erstes goto :-} durchfallen reicht nicht, weil auch eine 
			// andere body-schreib-methode eingestellt sein kann,)
			goto x;
				
		case ddwebconnection::write_buffer :
			
			// wir sind schon verkorkt ...also schreiben
	 		result = send_buffer( _outbody, ddwebconnection::write_header );
			// für den rest korken ziehen
			cork( false );
			return result;
	
		//case write_mmap : 			
		case ddwebconnection::write_sendfile :
			
				bytes = ::sendfile( _fd, _filefd, &_offset, _filesize );
				// schiefgegangen oder EAGAIN?
				if( bytes <= 0 )
				{
					if( errno == EAGAIN )
						return 0;
					// Böser Fehler
					return -1;
				}	
				// für den rest korken ziehen
				cork( false );
				
				// hat geklappt
				_filesize -= bytes;
		
		// einsprung, um bei not modified einen 
		// schreibversuch zu verhindern.
		
		case ddwebconnection::write_not_modified :		
				
				// fertig ?
				if( !_filesize )
				{
					::close( _filefd );
					_filefd = -1;
					return 1;
				}
				return 0;
	}
	
	return 1;
}


void ddwebbuffer::cork( bool usecork )
{
	// schon im gewünschten zustand ?
	if( _corked == usecork )
		return;
	_corked = usecork; 
	int cork = usecork ? 1 : 0;
	// Korken reinstecken / rausziehen
	setsockopt( _fd,IPPROTO_TCP,TCP_CORK, &cork, sizeof( cork ) );

}


/**
	Gibt 1 zurück, wenn der Buffer vollständig geschrieben wurde.
*/

int ddwebbuffer::send_buffer( ddiobuffer& buffer, int nextstate, int option )
{  
	
	// erstmal raushauen
	int bytes = ::send( _fd, buffer.output(), buffer.max_output(), option );
		
	// schiefgegangen oder EAGAIN?
	if( bytes <= 0 )
	{
		if( errno == EAGAIN )
			return 0;
		// Böser Fehler
		return -1;
	}
	
	buffer.move_output( bytes );
	// buffer noch nicht fertig?
	if( buffer.max_output() )
		return 0;
	
	// fertig
	set_state_flags( nextstate );
	return 1;

}


int ddwebbuffer::restore_buffers()
{
	// und 'shrinken'
	_outhead.shrink_buffer();
	_outbody.shrink_buffer();	
	// die 'workers' auf jeden fall zurücksetzen
	_outhead.reset();
	_outbody.reset();

	
	// das soll gar nicht vorkommen! Wenn ja, dann war 
	// das der 'out of filehandles' BUG
	if( _filefd > -1 )
		::close( _filefd );
	// defaults setzen
	set_state_flags( ddwebconnection::write_header );
	_nextmode = ddwebconnection::write_buffer;
	
	// reqs sind ok wenn genausoviel aus dem head
	// gelesen wie reingeschrieben wurde 
	// wartet noch ein request ?
	// ACHTUNG: Das geht schief bei BILD-Uploads
	//if( _input_offset != _output_offset )
	//	return 0; // im Sinne von EAGAIN, weitermachen
		
	// finished
	reset();
	shrink_buffer();
	
	return 1;

}

void ddwebbuffer::close()
{
	reset();
	_outhead.reset();
	_outbody.reset();
	if( _filefd > -1 )
		::close( _filefd );
	::close( _fd );
	// defaults setzen
	set_state_flags( ddwebconnection::write_header );
	_nextmode = ddwebconnection::write_buffer;
	
}

