#!/usr/local/bin/perl5 -s
#
# This file is part of SnarfNews
# Copyright (C) 1991,1992,1993,1994,1995,1996 Alec Muffett
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#

require "snarf.pl";

$usage = "gronknntp [-debug] [-save [-tree]] hostname[:port] [configfile]\n";

# where are we?
chop($homedir = `pwd`);

# where do we save stuff ?
$savedir = $ENV{"SNARFSAVE"};

# cheap filtering
$xpostmax = $ENV{"SNARFXPOSTMAX"};

# hostname[:port] to connect to
$host = shift || die $usage;

# config files (note that config file will require :port if specified)
$configfile = shift || "$ENV{SNARFCONF}/sites/$host";
$nconfigfile = "$configfile.NEW";
$oconfigfile = "$configfile.OLD";

# fetch method
$method = 0;

# open config files
unless (open(GRP, $configfile))
{
    die "gronknntp: open: $configfile: $!\n";
}

unless (open(NGRP, ">$nconfigfile"))
{
    die "gronknntp: open: $nconfigfile: $!\n";
}

# make latter unbuffered for disaster recovery
select((select(NGRP), $| = 1)[0]);

# connect
unless (&OpenNNTP($host))
{
    die "gronknntp: cannot open connection to $host\n";
}

# read a line to see if we got a good connection.
if (($_ = &RecvNNTP(0)) !~ /^200/o)
{
    die "gronknntp: service unavailable: $_";
}

# in case we run into an INN server
&SendNNTP("MODE reader\n");

if (($_ = &RecvNNTP(0)) !~ /^200/o)
{
    warn "gronknntp: server didn't like MODE command: $_";
}

# hack through the group list
while(<GRP>)                    # for each group
{
    # comment
    if (/^(\#|\s)/o)
    {
	print NGRP $_;
	next;
    }

    s/\#.*//o;                  # remove trailing comments

    ($groupname, $haveart) = split;

    # make sure the group change worked...
    &SendNNTP("GROUP $groupname\n");

    $_ = &RecvNNTP(0);

    ($retval, $numart, $firstart, $lastart) = split;

    if ($retval !~ /^211/o)
    {
	warn "gronknntp: bad group: $groupname: $_";
	print NGRP "$groupname $haveart\t\t\t# bad group?\n";
	next;
    }

    unless (chdir($homedir))
    {
	die "gronknntp: chdir: homedir $homedir: $!\n";
    }

    if ($save)
    {
	unless (-d $savedir)
	{
	    mkdir($savedir, 0700) || die "gronknntp: mkdir: $savedir: $!\n";
	}

	unless (chdir($savedir))
	{
	    die "gronknntp: chdir: $savedir: $!\n";
	}

	if ($tree)
	{
	    @dirs = split(/\./o, $groupname);

	    $dir = ".";

	    while (@dirs)
	    {
		$element = shift(@dirs);
		$dir .= "/$element";

		unless (-d $dir)
		{
		    mkdir($dir, 0700) || die "gronknntp: mkdir: $dir: $!\n";
		}
	    }

	    unless (chdir($dir))
	    {
		die "gronknntp: chdir: $dir: $!\n";
	    }
	}
	else
	{
	    unless (-d $groupname)
	    {
		mkdir($groupname, 0700) || 
		    die "gronknntp: mkdir: $groupname: $!\n";
	    }

	    unless (chdir($groupname))
	    {
		die "gronknntp: chdir: $groupname: $!\n";
	    }
	}
    }

    # skip if catchup or got everything
    if (($haveart < $lastart) && (!$catchup)) 
    {
	if ($method == 0)
	{
	    $method = 1
		unless (&FetchViaXhdr($firstart, $lastart, $haveart));
	}

	if ($method == 1)
	{
	    $method = 2
		unless (&FetchViaListgroup($haveart));
	}

	if ($method == 2)
	{
	    $method = 3
		unless (&FetchViaNext($firstart, $lastart, $haveart));
	}

	if ($method == 3)
	{
	    die "gronknntp: cannot find a fetch method\n";
	}
    }

    print NGRP "$groupname $lastart\n";
}

&SendNNTP("QUIT\n");

if (($_ = &RecvNNTP(0)) !~ /^205/o)
{
    warn "gronknntp: server didn't like QUIT command: $_";
}

&CloseNNTP;

close(GRP);
close(NGRP);

unless (chdir($homedir))
{
    die "gronknntp: chdir: homedir $homedir: $!\n";
}

unlink($oconfigfile);

unless (rename($configfile, $oconfigfile))
{
    die "gronknntp: rename: $configfile $oconfigfile: $!\n";
}

unless (rename($nconfigfile, $configfile))
{
    die "gronknntp: rename: $nconfigfile $configfile: $!\n";
}

exit(0);

##################################################################
##################################################################

sub FetchViaXhdr
{
    local($first, $last, $lwm) = @_;
    @artlist = ();              # Use global artlist for better memory usage

    if ($first > $lwm)
    {
	$lwm = $first;          # We missed a bunch
    } else
    {
	$lwm++;                 # Try for next article
    }

    if ($lwm > $last)
    {
	return 1; 
    }
    elsif ($lwm == $last)
    {
	&SendNNTP("XHDR Newsgroups $lwm\n");
    }
    else
    {
	&SendNNTP("XHDR Newsgroups $lwm-$last\n");
    }

    if (($_ = &RecvNNTP(0)) !~ /^221/o)
    {
	warn "gronknntp: $groupname: XHDR failed: $_";
	return undef;
    }

    while ($_ = &RecvNNTP(3))
    {
	last if ($_ eq ".\n");
	($art, $ngline) = split;

	$ngline =~ tr/,//cd;
	$xpostnum = length($ngline) + 1;

	printf(STDERR "- xhdr: article %d, %d/%d groups ",
	       $art, $xpostnum, $xpostmax) if ($debug);

	if ($xpostmax && ($xpostnum > $xpostmax))
	{
	    warn "[dropped]\n" if ($debug);
	}
	else
	{
	    warn "\n" if ($debug);
	    push(@artlist, $art);
	}
    }

    foreach $art (@artlist)
    {
	&SendNNTP("ARTICLE $art\n");

	if (($_ = &RecvNNTP(0)) !~ /^220/o)
	{
	    warn "gronknntp: $groupname: no article numbered $art: $_";
	    next;
	}

	&SlurpSave($art);
    }

    return 1;
}

##################################################################
##################################################################

sub FetchViaListgroup
{
    $lwm = shift;               # low water mark
    @artlist = ();              # Use global artlist for better memory usage

    &SendNNTP("LISTGROUP\n");

    if (($_ = &RecvNNTP(0)) !~ /^211/o)
    {
	warn "gronknntp: $groupname: LISTGROUP failed: $_";
	return undef;
    }

    while ($_ = &RecvNNTP(3))
    {
	last if ($_ eq ".\n");
	next if ($_ <= $lwm);
	s/\s+//go;
	push(@artlist, $_);
    }

    foreach $art (@artlist)
    {
	&SendNNTP("ARTICLE $art\n");

	if (($_ = &RecvNNTP(0)) !~ /^220/o)
	{
	    warn "gronknntp: $groupname: no article numbered $art: $_";
	    next;
	}

	&SlurpSave($art);
    }

    return 1;
}

##################################################################
##################################################################

sub FetchViaNext
{
    local($first, $last, $lwm) = @_;

    if ($first > $lwm)
    {
	$lwm = $first;          # We missed a bunch
    } else
    {
	$lwm++;                 # Try for next article
    }

    return 1 if ($art > $last);	# No point in carrying on

    $art = $lwm;		# where to start

    while (1)
    {
	while ($art <= $last)	# for all existing articles
	{
	    &SendNNTP("ARTICLE $art\n");

	    if (($_ = &RecvNNTP(0)) !~ /^220/o)	# try the next one
	    {
		warn "gronknntp: $groupname: no article numbered $art: $_";
		$art++;
	    }
	    else
	    {
		last;		# got what we wanted
	    }
	}

	last if ($art > $last);	# gone too far

	&SlurpSave($art);	# save what we've got

	if ($art == $last)	# if we just saved the last article
	{
	    last;		# quit
	}

	&SendNNTP("NEXT\n");	# ...ask to scroll to next

	if (($_ = &RecvNNTP(0)) !~ /^223\s+(\d+)/o)
	{
	    warn "gronknntp: $groupname: next failed: $_";
	    last;
	}

	$art = $1;		# ...and set up our new target
    }

    return 1;
}

##################################################################
##################################################################

sub SlurpSave
{
    $artnum = shift;
    @article = ();              # Destination for article
    $artsize = 0;               # Hook for rnews batching
    $ngline = undef;            # trap excessive xpostposting


    while ($_ = &RecvNNTP(3))
    {
	last if ($_ eq ".\n");

	s/^\.\./\./o;

	unless (defined($ngline))
	{
	    $ngline = $_ if (/^Newsgroups:/o);
	}

	push(@article, $_);
	$artsize += length($_);
    }

    # this code *may* be redundant, bwtf
    $ngline =~ tr/,//cd;
    $xpostnum = length($ngline) + 1;

    printf(STDERR "- article: number %d, %d bytes, %d/%d groups ",
	   $artnum, $artsize, $xpostnum, $xpostmax) if ($debug);

    if ($xpostmax && ($xpostnum > $xpostmax))
    {
	warn "[dropped]\n" if ($debug);
	return undef;
    }
    else
    {
	warn "\n" if ($debug);
    }

    if ($save)
    {
	unless (open(OUTFILE, ">$artnum"))
	{
	    die "gronknntp: open: $groupname: $artnum: $!\n";
	}
	print OUTFILE @article;
	close(OUTFILE);
    }
    else
    {
	print "#! rnews $artsize\n";
	print @article;
    }

    1;
}

##################################################################
##################################################################
