/***************************************************************************
                           casyncdns.cpp  -  description
                             -------------------
    begin                : Mon Jun 10 2002
    copyright            : (C) 2002-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 "casyncdns.h"
#include "cnetaddr.h"

#include <string.h>

#define HOST_LIST_TIMEOUT	60
#define HOST_CACHE_TIMEOUT	60

/** */
CAsyncDns::CAsyncDns()
{
	m_pHostCache = new CStringList<CAsyncDnsEntry>(4);
	m_pHostList  = new CStringList<CAsyncDnsEntry>(4);

	// start new thread
	Start();
}

/** */
CAsyncDns::~CAsyncDns()
{
	SetInstance(0);

	// stop thread
	Stop(true);

	Lock();

	delete m_pHostCache;
	m_pHostCache = 0;

	delete m_pHostList;
	m_pHostList = 0;

	UnLock();
}

/** call from application */
eAsyncDns CAsyncDns::GetHostI4( CString stringHost, struct sockaddr_in * sin, CString * errmsg )
{
	CAsyncDnsEntry * AsyncDnsEntry;
	eAsyncDns err;
	
	// return if lock failed
	if ( TryLock() == false )
	{
		return eadAGAIN;
	}

	err = eadERROR;	

	// sanity check
	if ( m_pHostCache && m_pHostList && (stringHost.NotEmpty()) && sin )
	{
		if ( m_pHostCache->Get( stringHost, &AsyncDnsEntry ) != -1 )
		{
			// host found in the m_pHostCache list, return first resolved ip
			memcpy( sin, &AsyncDnsEntry->m_sin, sizeof(struct sockaddr_in) );
			m_pHostCache->Del(stringHost);
			err = eadSUCCESS;
		}

		if ( err == eadERROR )
		{
			if ( m_pHostList->Get( stringHost, &AsyncDnsEntry ) == -1 )
			{
				// host not found in the m_pHostList, add it
				AsyncDnsEntry = new CAsyncDnsEntry();

				AsyncDnsEntry->m_sHost    = stringHost;
				AsyncDnsEntry->m_bError   = false;
				// set initial timeout
				AsyncDnsEntry->m_tTimeout = time(0);
				
				m_pHostList->Add( stringHost, AsyncDnsEntry );

				err = eadAGAIN;
			}
			else if ( AsyncDnsEntry->m_bError == false )
			{
				// dns query not finished
				err = eadAGAIN;
			}
			else
			{
				if ( errmsg != 0 )
				{
					// return error message
					*errmsg = AsyncDnsEntry->m_sErrMsg;
				}

				// remove from hostlist
				m_pHostList->Del(AsyncDnsEntry->m_sHost);
			}
		}
	}

	UnLock();

	return err;
}

/** */
void CAsyncDns::Thread()
{
	CAsyncDnsEntry * AsyncDnsEntry = 0;

	Lock();

	if ( m_pHostCache->Count() > 0 )
	{
		// check cache timeout to avoid possible list overflows
		while ( m_pHostCache->Next( &AsyncDnsEntry ) != 0 )
		{
			if ( (time(0)-AsyncDnsEntry->m_tTimeout) >= HOST_CACHE_TIMEOUT )
			{
				// remove entry from m_pHostCache list
				m_pHostCache->Del(AsyncDnsEntry->m_sHost);
				AsyncDnsEntry = 0;
				break;
			}
		}
	}
	
	AsyncDnsEntry = 0;
			
	if ( m_pHostList->Count() > 0 )
	{
		while ( m_pHostList->Next( &AsyncDnsEntry ) != 0 )
		{
			if ( AsyncDnsEntry->m_bError == false )
			{
				// no error
				break;
			}
			else if ( (time(0)-AsyncDnsEntry->m_tTimeout) >= HOST_LIST_TIMEOUT )
			{
				// timeout, remove host from m_pHostList
				m_pHostList->Del(AsyncDnsEntry->m_sHost);
				AsyncDnsEntry = 0;
				break;
			}
		}
	}

	if ( AsyncDnsEntry != 0 )
	{
		// no error, no timeout -> update entry
		CString s = AsyncDnsEntry->m_sHost;
		UnLock();
		UpdateEntry(s);
	}
	else
	{
		UnLock();
	}

	NanoSleep(50);
}

/** */
void CAsyncDns::UpdateEntry( CString stringHost )
{
	CAsyncDnsEntry * AsyncDnsEntry = 0;
	struct sockaddr_in sin;
	bool res;
	CString errmsg;

	// ask dns for ip
	res = CNetAddr::GetHostI4( stringHost.Data(), &sin, &errmsg );

	Lock();

	if ( m_pHostList->Get( stringHost, &AsyncDnsEntry ) == 0 )
	{
		if ( !res )
		{
			// error, update entry
			AsyncDnsEntry->m_bError   = true;
			// update timeout to avoid list overflow
			AsyncDnsEntry->m_tTimeout = time(0);
			AsyncDnsEntry->m_sErrMsg = errmsg;
		}
		else
		{
			// no error, remove from m_pHostList and update m_pHostCache
			AsyncDnsEntry = new CAsyncDnsEntry();

			AsyncDnsEntry->m_sHost    = stringHost;
			AsyncDnsEntry->m_bError   = false;
			// update timeout to avoid list overflow
			AsyncDnsEntry->m_tTimeout = time(0);
			memcpy( &AsyncDnsEntry->m_sin, &sin, sizeof(struct sockaddr_in) );

			m_pHostList->Del(stringHost);
			m_pHostCache->Add(stringHost,AsyncDnsEntry);
		}
	}

	UnLock();
}
