#! /bin/perl --    # -*-Perl-*-
#
# outflow-stat - display outgoing feed statistics (#articles)
#                this works for innfeed and nntpsend/innxmit only
#
# History: This program is based on some ideas (and source code lines) of
#          Felix Kugler, SWITCH, inflow-package and could be part of it.
#
# 961029 V1.0   released
# 970109 V1.1   change ls to readdir for sitelist
#               fix strip path statement
# 970113 V1.2   aligned some paths, renumbered version (FK)
# 970128 V1.2.1	added config of innfeeds checkpointing (GW)
# 970220 V1.2.2	enriched outfile naming (FK)
# 970224 V1.2.3	innfeed.0.10 support added (FK)
# 970305 V1.2.4	read sitelist from conffile instead lockfiles (GW)
# 970310 V1.2.5	minor bug fixes concerning innfeed.conf parsing  
# 970705 V1.2.6 multiple innfeed support (FK)
# 971012 V1.3.0 nntpsend support, several minor modifications (FK)
# 971016 V1.3.1 improved handling of html-includes (FK)
# 971027 V1.3.2 added opt. external config file (FK)
# 971029 V1.4   added approximate line count (GW)
#               interactive calculation for one site
# 971109 V1.4.2 minor cosmetical modifications
# 971112 V1.5.0 version number aligned

$Copy="(c) 1996 Gerhard.Winkler\@univie.ac.at";

$RELDATE = "Wed Nov 12 11:16:02 MET 1997";
$RELEASE = "V1.5.0";
#
#
## local settings ----------------------------------------------------
#
$INFLOWCONF = "/home/news/config/inflow.conf"; # ext. local configs (optional)
#
## the following settings may be modified by an external config file
#
# innfeed stuff
$IFSPOOLDIR   = "/var/spool/news/out.going";
@INNFEEDCONF  = ( '/usr/local/news/inn/innfeed.conf',
		 '/usr/local/news/inn/innfeed.conf-1',
		 '/usr/local/news/inn/innfeed.conf-2',
		 '/usr/local/news/inn/innfeed.conf-3');
$OLDCONFSTYLE = 0;       # pre-0.10 innfeed uses old format for innfeed.conf
$CHKPT_FL     = 0;       # some innfeed versions use .checkpoint files
                         # some use .input for storing checkpoint info (>0.9.3)
#
# approximate line count stuff
# please check this value; it is site dependant
$MAGIC = 54.1;           # approximate line length in spool file
$THRES_MAGIC = 5410000;  # do exact line count if filesize is smaller than
                         # threshold else do approximate line count
#
# nntpsend stuff
$NSSPOOLDIR   = "/var/news/out.going";
@NNTPSENDCONF = ( '/home/news/config/nntpsend.ctl',);
@NSPOSTFIXES  = ( '', '!n', '.work', '.nntp' );
#
# naming stuff
%ABBREV     = ( 'innfeed', 'innfeed',
		'nntpsend', 'nntpsend',
	       );
$OUTFILE      = "/opt/www/docs/news/stat/outflow";  # generic outfile name
$WANTHOSTNAME = 0;       # add hostname to outfile name ( -host) ?
$GENERICTITLE = "Status of outgoing feeds";
#
# external programs & config files
@ADDTOPATH = ( '/opt/local/bin','/opt/gnu/bin' );
#
$DATE         = "date";
$WC           = "wc";
$GREP         = "grep";
$UNAME        = "uname";
$RESOLVCONF   = "/etc/resolv.conf";
#
# where to get this software
$INFLOWURL    = "ftp://ftp.switch.ch/info_service/netnews/wg/tools/inflow.tar.gz";
#
# local HTML stuff: you can put custom header & footer information into 
#                   local files; optional...
#
$HTMLHEADER = "/usr/local/news/stat/inflowhtmlheader";
$HTMLFOOTER = "/usr/local/news/stat/outflowhtmlfooter";
# 
# end local settings ------------------------------------------------

# initialisation ----------------------------------------------------
#
require "getopts.pl";

($path,$0) = ($0 =~ /^(.*)\/([^\/]+)$/);                # strip path...
umask 022;                                # file mask

&Getopts('adf:hi:wF');

&modify_config;
&update_PATH;
&gethostandfqdn;


$Usage="$0    -  $Copy

Release $RELEASE  of $RELDATE

Process information from innfeed spoolfiles in $IFSPOOLDIR
and nntpsend batches in $NSSPOOLDIR. Show 
how many articles are currently waiting be sent to the remote site.
Output available either in Text or HTML format.

Usage: 	$0 [-adhiwF] [-f<configfile>]  

Parameters:

   -a:            approximate line count for batches >$THRES_MAGIC byte,
		  taking an average line lenght of $MAGIC byte as basis. 
   -d:            turn on verbosity; for debugging only
   -f<configfile>:load external configuration file (default: $inflowconf)
   -h:            This help.
   -i <sitename>: calculate this site only
   -w:            WWW support: show results in HTML table format
   -F:            write to file (defined in script header)

external config file: $inflowconfinfo

Optional HTML-stuff:

header file: $HTMLHEADER
footer file: $HTMLFOOTER

Bugs: does not support nntplink currently.

\n";

$date = `$DATE`;
if ($WANTHOSTNAME) { $OUTFILE .= "-$hostname"; }
$titleline = "$GENERICTITLE on $fqdn";

if ($opt_h) { print "$Usage"; exit 0; }

if ($opt_F) {
    if ($opt_w) { $OUTFILE .= ".html"; }
    open(OUT,">$OUTFILE") || die "can't open $OUTFILE\n";
} else {
    open(OUT,">-");
}

#  check for custom header & footer files
if ($opt_w) {
    if (open(IN,$HTMLHEADER)) {  
	while (<IN>) {
	    $line = &interpret;
	    $htmlheader .= $line;
	}
	close(IN); 
    } else {
	$htmlheader = "<html><head>
          <!--Content-type: text/html\nRefresh: 60-->\n
          <title>$titleline</title></head><body><h2>$titleline</h2>\n";
    }
    if (open(IN,$HTMLFOOTER)) { 
	while (<IN>) {
	    $line = &interpret; 
	    $htmlfooter .= $line; 
	}
	close(IN);
    } else {
	$htmlfooter = "</body></html>\n";
    }
}

&readconf;
&onesite if ($opt_i);
&calculate;
&initpage;
&writedata;
&closepage;


# calculate
# -----------------------------------------------------------------------------
# main calculation part
# we go through the sitelist once for every feedtype supported
# and sort the results at the end...
sub calculate {
    # innfeed
    if (chdir $IFSPOOLDIR) {
	foreach $site (sort keys %sitelist) {
            $appr = 0;
	    next if ($feedprog{$site} ne 'innfeed');
	    $file = $site . ".lock";
	    unless (-e $file) {
		print "no lock file found: $site\n" if ($opt_d);
		&printsite("no data");
		next;
	    }
	    print "check site: $site\n" if ($opt_d);
	    
	    $scount = $icount = $ocount = $offset = 0;
	    $file = $site;
	    if (-e $file) {
		$scount = &countfile($file);
		print "found articles in $file: $scount\n" if ($opt_d);
	    } else {
		print "no $file\n" if ($opt_d);
	    }
	    
	    $file = $site . ".input";
	    if (-e $file) {
		$icount = &countfile($file);
		print "found articles in $file: $icount\n" if ($opt_d);
		if ($CHKPT_FL) { $check = $site . ".checkpoint"; }
		else { $check = $site . ".input"; }
		$offset = 0;
		if (open(CFL,"<$check")) {
		    $offset = <CFL>;
		    chop($offset);
		    close(CFL);
		}
		print "found offset: $offset\n" if ($opt_d);
		@status = stat($file);
		$size = @status[7];
		$icount = $icount - ($icount * $offset / $size);
		$icount = int($icount);
		print "found articles in $file minus offset: $icount\n" if ($opt_d);
	    } else {
		print "no $file\n" if ($opt_d);
	    }
	    
	    $file = $site . ".output";
	    if (-e $file) {
		$ocount = &countfile($file);
		print "found articles in $file: $ocount\n" if ($opt_d);
	    } else {
		print "no $file\n" if ($opt_d);
	    }
	    
	    $count = $scount + $icount + $ocount;
	    &printsite($count);
	}
    }

    # nntpsend
    if (chdir $NSSPOOLDIR) {
	foreach $site (sort keys %sitelist) {
            $appr = 0;
	    next if ($feedprog{$site} ne 'nntpsend');
	    warn "check site $site\n" if $opt_d;
	    #
	    $count = $c = 0;
	    #
	    foreach $postfix (@NSPOSTFIXES) {
		$file = $site . $postfix;
		if (-e $file) {
		    $c = &countfile($file);
		    warn "nntpsend batch $file contains $c articles\n" if $opt_d;
		    $count += $c;
		} else {
		    warn "no file $file found in $NSSPOOLDIR\n" if $opt_d; 
		}
	    }
	    #
	    &printsite($count);
	}
    }
}


# countfile
# -----------------------------------------------------------------------------
# count number of articles (lines) in file
# maybe there will be some size (byte) of articles in it
sub countfile {
   local($f) = @_;
   local($ret,$c,$s);
   if ($opt_a) {
      @status = stat($f);
      $s = @status[7];
      if ($s > $THRES_MAGIC) {
         print "using approximate line count for $f\n" if ($opt_d);
         $appr = 1;
         $c = $s / $MAGIC;
         $c = int($c);
      }
      else {
         $ret = `$WC -l $f`;
         ($c) = ($ret =~ /^\s*(\d+)/);
      }
   }
   else {
      $ret = `$WC -l $f`;
      ($c) = ($ret =~ /^\s*(\d+)/);
   }
   return($c);
}


# readconf
# -----------------------------------------------------------------------------
# read innfeed/nntpsend configuration files and extract site names
# support for multiple conf files
sub readconf {
    # innfeed
    foreach $conffile (@INNFEEDCONF) {
	unless (open(F,"<$conffile")) {
	    warn "can\'t open innfeed config file $conffile\n" if $opt_d;
	    next;
	}
	$conffilefound = 1;
	warn "loading conf file: $conffile\n" if $opt_d;
	if ($OLDCONFSTYLE) {       # innfeed 0.9.3 and older
	    while(<F>) {
		next if (/^\#/);
		next if (/^$/);
		next if (/^default:/);
		($peer,$ipname) = split(/:/);
		$sitelist{$peer}++;
		$conflist{$peer} = $ipname;
	    }
	}
	else {                     # innfeed 0.10 and newer
	    while(<F>) {
		next if (/^\#/);
		next if (/^$/);
		if (/^\s*peer\s+([^{}\s]+)/) { 
		    $peer = $1; 
		    $sitelist{$peer}++;
		}
		if (/ip-name:\s+(\S+)/ && $peer) {
		    $ipname = $1;
		    $conflist{$peer} = $ipname;
		    $feedprog{$peer} = 'innfeed';
		    print "conflist{$peer} = $ipname\n" if $opt_d;
		    $peer = "";
		}
	    }
	}
	close(F);
    }

    # nntpsend
    foreach $conffile (@NNTPSENDCONF) {
	unless (open(F,"<$conffile")) {
	    warn "can\'t open nntpsend config file $conffile\n" if $opt_d;
	    next;
	}
	$conffilefound = 1;
	while(<F>) {
	    next if (/^\#/);
	    next if (/^$/);
	    next if (/^default:/);
	    ($peer,$ipname) = split(/:/);
	    $sitelist{$peer}++;
	    $conflist{$peer} = $ipname;
	    $feedprog{$peer} = 'nntpsend';
	}


    }
    die "couldn\'t find any config files - aborting\n" unless $conffilefound;
    if ($opt_d) {
       print "\nfound following sites:\n";
       foreach $i (sort keys %conflist) {
          print "$i: $conflist{$i}\n";
       }
       print "\n\n";
    }
}



# onesite
# -----------------------------------------------------------------------------
# calculate for one site

sub onesite {
   local($i,$s);
   $s = $opt_i;

   print "\ncalculate onesite: $s\n" if ($opt_d);
   foreach $i (keys %sitelist) {
      if ($i ne $s) { delete $sitelist{$i}; }
   }
   if ((keys %sitelist) == 0) {
      print "site $s not found in config --> abort\n";
      exit;
   }

}



# printsite
# -----------------------------------------------------------------------------
# assemble result line for one site, do NOT print yet !!

sub printsite {
   local($c) = @_;
   if ($opt_w) {
      $c = "~ " . $c if ($appr);
      $result{$site} = "<TR>
                 <TD> $site </TD>
                 <TD> $conflist{$site} </TD>
                 <TD align=center> $ABBREV{$feedprog{$site}} </TD>
                 <TD align=right> $c </TD>
                 </TR>\n";
   } else {
      $c = "~ " . $c if ($appr);
      $result{$site} = sprintf("%15s %35s %4s %10s\n",
	     $site,$conflist{$site},,$ABBREV{$feedprog{$site}},$c);
   }
}


# initpage
# -----------------------------------------------------------------------------
# Print Page Headers 
#
sub initpage {
    if ($opt_w) { 
	print OUT "$htmlheader
                   <p><b>calculated at:</b> $date</p>\n";
        print OUT "<center><TABLE BORDER>
                   <TR><TH align=left>Feedname</TH>
                   <TH align=left>Sitename</TH>
                   <TH align=center>Type</TH>
                   <TH>Articles to transfer</TH>
                   </TR>\n";
    } else {
        print OUT "$titleline\n\n";
        print OUT "calculated at: $date\n";
	printf(OUT "%15s %35s %4s %10s\n\n",
	       "Feedname","Sitename","Type","Articles to transfer");
    }
}


# writedata
# -----------------------------------------------------------------------------
# sort and write results
#
sub writedata {
    foreach $site (sort keys (%result)) {
	print OUT "$result{$site}\n";
    }
}


# closepage
# -----------------------------------------------------------------------------
# close page, write footers...
#
sub closepage {
    if ($opt_w) {
        print OUT "</table></center><P><P>
<FONT SIZE=\"-1\">Table created by $0 $RELEASE.
<A HREF=\"$INFLOWURL\">inflow</A> is a project of the
<A HREF=\"http://www.switch.ch/netnews/wg/netnews-wg.html\">
RIPE netnews-wg</A>.</FONT><P><P>
$htmlfooter\n";
    } else { 
	print OUT "\n\n"; 
    }
}


# gethostandfqdn
#---------------------------------------------------------------------- 
# construct fully qualified domain name...
#
sub gethostandfqdn {
    chop($str=`$UNAME -n`);
    if ($str =~ /\./) {             # str is fqdn
	$fqdn = $str;
	($hostname) = ($str =~ /^([^.]+)\./);
    } else {                        # str is simple hostname
	$hostname = $str;
	$str = `$GREP domain $RESOLVCONF`;
	$str =~ /domain\s*(\S+)$/;
	$fqdn = $hostname . "." . $1;
    }
}


# update_PATH
# -----------------------------------------------------------------------------
# enhance PATH to support scripts run from cron
#
sub update_PATH {
    warn "OLD PATH=$ENV{'PATH'}\n" if $opt_d;
    @ENVPATH = split(/:/,$ENV{'PATH'});
    push(@ENVPATH,@ADDTOPATH);
    
    $ENV{'PATH'}='';
    foreach $p (@ENVPATH) {
	next if $envpathseen{$p}; # skip duplicates
	$envpathseen{$p} = 1;
	$ENV{'PATH'} .= $p . ":";
    }
    chop($ENV{'PATH'});           # cut last ":"
    warn "NEW PATH=$ENV{'PATH'}\n" if $opt_d;
}


# interpret
# -----------------------------------------------------------------------------
# interpret variable read from file; variables have to be separated by
# white space
#
sub interpret {
    local($line) = ""; 
    @words = split;
    foreach $w (@words) {
	if ($w =~ /^\$/) { eval "\$w=$w"; }
	$line .= "$w ";
    }
    return $line;
}



# modify_config
# -----------------------------------------------------------------------------
#
sub modify_config {
    local($p,$n);
    $inflowconf = $opt_f ? $opt_f : $INFLOWCONF;
    if ($inflowconf =~ /^(.*)\/([^\/]+)$/) {
	$p = $1; 
	$n = $2;
	push(@INC,$p);
    } else { 
	$n = $inflowconf; 
    }
    warn "p=$p  n=$n  inflowconf=$inflowconf\n" if $opt_d;
    if (-e $inflowconf) {
	warn "loading local configs from $inflowconf...\n" if $opt_d;
	require $n;
	$inflowconfinfo = "$CFRELEASE - $inflowconf";
    } else {
	warn "didn't find any local configs $inflowconf on INC=@INC\n" if $opt_d;
	$inflowconfinfo = "- none -";
    }
}

