/*
  fetch.c

  $Id: fetch.c 316 2001-10-31 11:44:53Z bears $
*/

#if HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <errno.h>

#if TIME_WITH_SYS_TIME
#include <sys/time.h>
#include <time.h>
#else
#if HAVE_SYS_TIME_H
#include <sys/time.h>
#else
#include <time.h>
#endif
#endif

#include <signal.h>
#include "client.h"
#include "configfile.h"
#include "content.h"
#include "dynamicstring.h"
#include "fetch.h"
#include "fetchlist.h"
#include "request.h"
#include "group.h"
#include "lock.h"
#include "log.h"
#include "outgoing.h"
#include "protocol.h"
#include "pseudo.h"
#include "util.h"
#include "portable.h"

#define	MAX_ARTICLE_CMDS_QUEUED		20

struct Fetch
{
    Bool ready;
    Str serv;
} fetch = { FALSE, "" };

static Bool
connectToServ( const char *name )
{
    Log_inf( "Fetch from '%s'", name );
    if ( ! Client_connect( name ) )
    {
        Log_err( "Could not connect to %s", name );
        return FALSE;
    }
    return TRUE;
}

Bool
Fetch_getNewGrps( void )
{
    time_t t;
    Str file;

    ASSERT( fetch.ready );
    snprintf( file, MAXCHAR, "%s/lastupdate.%s",
	      Cfg_spoolDir(), fetch.serv );
    if ( ! Utl_getStamp( &t, file ) )
    {
        Log_err( "Cannot read %s. Please run noffle --query groups", file );
        return FALSE;
    }
    Log_inf( "Updating groupinfo" );

    /*
     * If NEWGROUPS fails, it isn't necessarily fatal. You can do
     * a periodic noffle --query groups to refresh your group list.
     * So only return failure here if the status indicates the link
     * itself failed.
     *
     * In particular, older versions of NNTPcache have a Y2K bug that
     * stops NEWGROUPS working.
     */
    return ( ! IS_FATAL( Client_getNewgrps( &t ) ) );
}

/* Databases open on entry, closed on exit. */
static int
fetchNewArts( const char *name, FetchMode mode )
{
    int next, first, last, refetch, stat;

    stat = Client_changeToGrp( name );
    if ( stat != STAT_OK )
    {
        Log_err( "Could not change to group %s", name );
	if ( Lock_gotLock() )
	    Lock_closeDatabases();
        return stat;
    }
    Client_rmtFirstLast( &first, &last );
    Cont_read( name );
    next = Grp_rmtNext( name );
    if ( next == GRP_RMT_NEXT_NOT_SUBSCRIBED )
	next = first;
    if ( next == last + 1 )
    {
        Log_inf( "No new articles in %s", name );
        Cont_write();
        Grp_setFirstLast( name, Cont_first(), Cont_last() );
	Lock_closeDatabases();
        return STAT_OK;
    }
    if ( first == 0 && last == 0 )
    {
        Log_inf( "No articles in %s", name );
        Cont_write();
        Grp_setFirstLast( name, Cont_first(), Cont_last() );
	Lock_closeDatabases();
        return STAT_OK;
    }
    if ( next > last + 1 )
    {
    	refetch = last - Cfg_maxFetch() + 1;
    	if ( refetch < 0 ) refetch = 1;
        Log_err( "Article number inconsistent (%s rmt=%lu-%lu, next=%lu). "
		 "Refetching from %lu",
                 name, first, last, next, refetch );
        Pseudo_cntInconsistent( name, first, last, next, refetch );
        first = refetch;
    }
    else if ( next < first )
    {
        Log_inf( "Missing articles (%s first=%lu next=%lu)",
                 name, first, next );
        Pseudo_missArts( name, first, next );

	/*
	 * If we are missing articles but there are none to fetch,
	 * we must ensure we don't repeatedly generate missing
	 * article warning on every fetch until there is something
	 * to fetch. To guard against this, update the group remote
	 * next now.
	 */
	Grp_setRmtNext( name, first );
	next = first;
    }
    else
        first = next;
    if ( last - first > Cfg_maxFetch() )
    {
        Log_ntc( "Cutting number of overviews to %lu", Cfg_maxFetch() );
        first = last - Cfg_maxFetch() + 1;
    }
    Log_inf( "Getting remote overviews %lu-%lu for group %s",
             first, last, name );
    Lock_closeDatabases();
    return Client_getOver( name, first, last, mode );
}

Bool
Fetch_getNewArts( const char *name, FetchMode mode )
{
    if ( ! Lock_openDatabases() )
    {
        Log_err( "Could not open message base" );
        return FALSE;
    }
    return fetchNewArts( name, mode );
}

Bool
Fetch_updateGrps( void )
{
    FetchMode mode;
    int i, size;
    const char *name;

    ASSERT( fetch.ready );
    if ( ! Lock_openDatabases() )
    {
        Log_err( "Could not open message base" );
        return FALSE;
    }
    Fetchlist_read();
    size = Fetchlist_size();
    for ( i = 0; i < size; ++i )
    {
        Fetchlist_element( &name, &mode, i );
        if ( strcmp( Grp_server( name ), fetch.serv ) == 0 )
	{
            if ( IS_FATAL( fetchNewArts( name, mode ) ) )
		return FALSE;
	    if ( ! Lock_openDatabases() )
	    {
		Log_err( "Could not open message base" );
		return FALSE;
	    }
	}
    }
    Lock_closeDatabases();
    return TRUE;
}

static int
fetchMessageList( const char *list, int *artcnt, int artmax )
{
    const char *p;
    Str msgId;
    int stat;

    ASSERT( Lock_gotLock() );
    stat = Client_retrieveArtList( list, artcnt, artmax );
    if ( IS_FATAL( stat ) )
	return stat;
    p = list;
    while ( ( p = Utl_getLn( msgId, p ) ) )
        Req_remove( fetch.serv, msgId );
    return STAT_OK;
}

Bool
Fetch_getReq_( void )
{
    Str msgId;
    DynStr *list;
    DynStr *fetchList;
    const char *p;
    int count = 0, artcnt = 0, artmax = 0;
    Bool res;
    int stat;

    ASSERT( fetch.ready );
    Log_dbg( LOG_DBG_FETCH, "Retrieving articles marked for download" );
    list = new_DynStr( 10000 );
    fetchList = new_DynStr( 1000 );
    if ( list == NULL || fetchList == NULL )
    {
	if ( list != NULL )
	    del_DynStr( list );
        Log_err( "Out of memory in Fetch_get_Req_");
	return FALSE;
    }

    /*
     * Get all waiting message IDs for this server. We copy into a master
     * list as the requests file will be closed and re-opened during the
     * fetch and the position therein will be lost.
     */
    if ( ! Lock_openDatabases() )
    {
        Log_err( "Could not open message base" );
        return FALSE;
    }

    if ( Req_first( fetch.serv, msgId ) )
    {
        do
        {
            DynStr_appLn( list, msgId );
	    artmax++;
        }
        while ( Req_next( msgId ) );
	Log_inf( "%d TOTAL messages to download", artmax);
    }

    /* Retrieve in groups of up to size MAX_ARTICLE_CMDS_QUEUED. */
    p = DynStr_str( list );
    res = TRUE;
    while ( res && ( p = Utl_getLn( msgId, p ) ) != NULL )
    {
	DynStr_appLn( fetchList, msgId );
	if ( ++count % MAX_ARTICLE_CMDS_QUEUED == 0 )
	{
	    stat = fetchMessageList( DynStr_str( fetchList ), &artcnt,
				     artmax );
	    res = ! IS_FATAL( stat );
	    DynStr_clear( fetchList );
	}
    }
    stat = fetchMessageList( DynStr_str( fetchList ), &artcnt, artmax );
    res = res && ! IS_FATAL( stat );

    del_DynStr( fetchList );
    del_DynStr( list );
    Lock_closeDatabases();
    return res;
}

static void
returnArticleToSender( const char *sender, const char *reason,
                       const char *article )
{
    int ret;
    Str cmd;
    FILE *f;
    sig_t lastHandler;

    Log_err( "Return article to '%s' by mail", sender );
    snprintf( cmd, MAXCHAR, "%s -t -oi", SENDMAILPROG);
    lastHandler = signal( SIGPIPE, SIG_IGN );
    f = popen( cmd, "w" );
    if ( f == NULL )
        Log_err( "Invocation of '%s' failed (%s)", cmd, strerror( errno ) );
    else
    {
        fprintf( f,
                 "To: %s\n"
                 "Subject: [ NOFFLE: Posting failed ]\n"
                 "\n"
                 "\t[ NOFFLE: POSTING OF ARTICLE FAILED ]\n"
                 "\n"
                 "\t[ The posting of your article failed. ]\n"
                 "\t[ Reason of failure at remote server: ]\n"
                 "\n"
                 "\t[ %s ]\n"
                 "\n"
                 "\t[ Full article text has been appended. ]\n"
                 "\n"
                 "%s"
                 ".\n",
                 sender,
                 reason, article );
        ret = pclose( f );
        if ( ret != EXIT_SUCCESS )
            Log_err( "'%s' exit value %d", cmd, ret );
        signal( SIGPIPE, lastHandler );
    }
}

Bool
Fetch_postArts( void )
{
    DynStr *s;
    Str msgId, errStr, sender;
    const char *txt;
    Bool res;

    res = TRUE;
    s = new_DynStr( 10000 );
    if ( Out_first( fetch.serv, msgId, s ) )
    {
        Log_inf( "Posting articles" );
        do
        {
            txt = DynStr_str( s );
	    if ( Client_postArt( msgId, txt, errStr ) != STAT_OK )
	    {
		res = FALSE;
		break;
	    }
	    
	    /*
	     * OK, no server communication SNAFU during post. Now, do we
	     * get an error response? If so, try to return article to sender.
	     */
	    Out_remove( fetch.serv, msgId );
	    if ( errStr[0] != '\0' )
	    {
		Utl_cpyStr( sender, Cfg_mailTo() );
		if ( strcmp( sender, "" ) == 0
		     && ! Prt_searchHeader( txt, "SENDER", sender )
		     && ! Prt_searchHeader( txt, "X-NOFFLE-X-SENDER",
					    sender ) /* see server.c */
		     && ! Prt_searchHeader( txt, "FROM", sender ) )
		    Log_err( "Article %s has no From/Sender/X-Sender field",
			     msgId );
		else
		    returnArticleToSender( sender, errStr, txt );
	    }
        }
        while ( Out_next( msgId, s ) );
    }
    del_DynStr( s );
    return res;
}

Bool
Fetch_init( const char *serv )
{
    Lock_closeDatabases();
    if ( ! connectToServ( serv ) )
    {
        Lock_openDatabases();
        return FALSE;
    }
    Utl_cpyStr( fetch.serv, serv );
    fetch.ready = TRUE;
    return TRUE;
}

void
Fetch_close()
{
    Client_disconnect();
    fetch.ready = FALSE;
    Log_inf( "Fetch from '%s' finished", fetch.serv );
    Lock_openDatabases();
}
