#!/usr/bin/perl -w
#
#  Show the top N entries from the MP3d logfile.
#

use Getopt::Long;
use strict;

#  The logfile that we read.
my $DEFAULT_CONFIG = "/etc/mp3d.conf";

#
#  Options set by the command line arguments.
#
my $SHOW_SONGS = 0;
my $SHOW_SEARCH=0;
my $SHOW_DIRS = 0;
my $SHOW_USERS = 0;
my $SHOW_HELP = 0;
my $SHOW_VERSION = 0;
my $SHOW_NUMBER  = 20;
my $SHOW_PLAIN  = 0;
my $SHOW_AGENTS = 0;



# Current version string
my $VERSION_NUMBER = '$Revision: 1.24 $';




#  Parse the options.
&parseArguments();

#  Start the script proper.
&main();




#
#  Call routines to do our work, depending on the state of
# the options that we've been given.
#
sub main()
{
    #
    # Handle the --help, and --version flags first.
    #
    if ( $SHOW_HELP )
    {
        &showHelp();
        exit;
    }
    if ( $SHOW_VERSION )
    {
        &showVersion();
        exit;
    }

    if ( $SHOW_SONGS )
    {
	&showTopSongs( $SHOW_NUMBER );
	exit;
    }
    if ( $SHOW_DIRS )
    {
	&showTopDirs( $SHOW_NUMBER );
	exit;
    }
    if ( $SHOW_USERS )
    {
	&showTopUsers( $SHOW_NUMBER );
	exit;
    }
    if ( $SHOW_SEARCH )
    {
	&showTopSearches( $SHOW_NUMBER );
	exit;
    }
    if ( $SHOW_AGENTS )
    {
	&showTopAgents( $SHOW_NUMBER );
	exit;
    }


    &showHelp();
    exit;

}



sub showTopSearches( $ )
{
    my ( $count ) = (@_);
    my %SEARCHES;

    my @lines = getLogfile();
    foreach my $line ( @lines )
    {
	if ( $line =~ /\"GET \/search\?q=([^\"]+)\"/ )
	{
	    my $entry = $1;
	    $entry = &removeDuplicateSlashes( $entry );
	    $SEARCHES{ $entry } ++;
	}
    }

    if ( ! $SHOW_PLAIN )
    {
	print "<TR><TD><B>Count</B></TD><TD><B>Search Terms</B></TD></TR>\n";
    }
    else
    {
	print "Count\t\t\tSearch Terms\n";
    }


    my $open = "";
    my $mid  = "\t\t";
    my $close = "";

    if ( ! $SHOW_PLAIN )
    {
	$open = "<TR><TD>";
	$mid  = "</TD><TD>";
	$close = "</TD></TR>";
    }

    foreach my $song (sort { $SEARCHES{$b} <=> $SEARCHES{$a} }
		            keys %SEARCHES) 
    {
	$count -= 1;

	if ( ! $SHOW_PLAIN  )
	{
	    print $open .  $SEARCHES{$song} . $mid . 
		"<A HREF=\"/search?q=" . $song . "\">$song</A>" . $close . "\n";
	}
	else
	{
	    print $open . $SEARCHES{$song} . $mid . $song . $close . "\n";
	}

	return if $ count le 0;
    }
}


#
# Show the top N user agents.
#
sub showTopAgents( $ )
{
    my ( $count ) = (@_);
    my %AGENTS;

    my @lines = getLogfile();
    foreach my $line ( @lines )
    {
	if ( $line =~ /\"\" \"([^\"]+)\"/ )
	{
	    my $entry = $1;
	    $AGENTS{ $entry } ++;
	}
    }

    if ( ! $SHOW_PLAIN )
    {
	print "<TR><TD><B>Count</B></TD><TD><B>User-Agent</B></TD></TR>\n";
    }
    else
    {
	print "Count\t\t\tUser-Agent\n";
    }


    my $open = "";
    my $mid  = "\t\t";
    my $close = "";

    if ( ! $SHOW_PLAIN )
    {
	$open = "<TR><TD>";
	$mid  = "</TD><TD>";
	$close = "</TD></TR>";
    }

    foreach my $song (sort { $AGENTS{$b} <=> $AGENTS{$a} }
		            keys %AGENTS) 
    {
	$count -= 1;

	if ( ! $SHOW_PLAIN  )
	{
	    print $open .  $AGENTS{$song} . $mid . $song . $close . "\n";
	}
	else
	{
	    print $open . $AGENTS{$song} . $mid . $song . $close . "\n";
	}

	return if $ count le 0;
    }
}


sub showTopSongs( $ )
{
    my ( $count ) = (@_);
    my %SONGS;

    my $alwaysStream = alwaysStream();
    my @lines = getLogfile();
    foreach my $line ( @lines )
    {
	if ( $line =~ /\"GET ([^\"]+)\"/ )
	{
	    my $entry = $1;
	    if (  $entry =~ /\/$/ )
	    {
                # print "Ignoring directory : $entry\n";
	    }
	    elsif ( $entry =~ /^\/search\?[pq]=/ )
	    {
		# print "Ignoring search request: $entry\n";
	    }
	    else
	    {
		if ( $entry =~ /\.m3u$/i )
		{
#		    print "Ignoring playlist.\n";
		}
		else
		{
		    $entry = &removeDuplicateSlashes( $entry );

		    if ( $alwaysStream )
		    {
			$entry .= ".m3u";
		    }
		    $SONGS{ $entry } ++;
		}
	    }
	}
    }

    if ( ! $SHOW_PLAIN )
    {
	print "<TR><TD><B>Count</B></TD><TD><B>Song</B></TD></TR>\n";
    }
    else
    {
	print "Count\t\t\tSong\n";
    }


    my $open = "";
    my $mid  = "\t\t";
    my $close = "";

    if ( ! $SHOW_PLAIN )
    {
	$open = "<TR><TD>";
	$mid  = "</TD><TD>";
	$close = "</TD></TR>";
    }

    foreach my $song (sort { $SONGS{$b} <=> $SONGS{$a} }
		            keys %SONGS) 
    {
	$count -= 1;

	if ( ! $SHOW_PLAIN  )
	{
	    print $open .  $SONGS{$song} . $mid . 
		"<A HREF=\"" . $song . "\">$song</A>" . $close . "\n";
	}
	else
	{
	    print $open . $SONGS{$song} . $mid . $song . $close . "\n";
	}

	return if $ count le 0;
    }
}



sub showTopDirs( $ )
{
    my ( $count ) = (@_);
    my %SONGS;

    my @lines = getLogfile();
    foreach my $line ( @lines )
    {
	if ( $line =~ /\"GET ([^\"]+)\"/ )
	{
	    my $entry = $1;
	    if ( $entry =~ /\/$/ )
	    {
		# print "Found dir $entry\n";
		$entry = &removeDuplicateSlashes( $entry );
		$SONGS{ $entry } ++;
	    }
	}
    }

    if ( ! $SHOW_PLAIN )
    {
	print "<TR><TD><B>Count</B></TD><TD><B>Directory</B></TD></TR>\n";
    }
    else
    {
	print "Count\t\t\tDirectory\n";
    }


    my $open = "";
    my $mid  = "\t\t";
    my $close = "";

    if ( ! $SHOW_PLAIN )
    {
	$open = "<TR><TD>";
	$mid  = "</TD><TD>";
	$close = "</TD></TR>";
    }

    foreach my $song (sort { $SONGS{$b} <=> $SONGS{$a} }
		            keys %SONGS) 
    {
	$count -= 1;
	if ( ! $SHOW_PLAIN  )
	{
	    print $open .  $SONGS{$song} . $mid . 
		"<A HREF=\"" . $song . "\">$song</A>" . $close . "\n";
	}
	else
	{
	    print $open . $SONGS{$song} . $mid . $song . $close . "\n";
	}

	return if $ count le 0;
    }
}

sub showTopUsers( $SHOW_NUMBER )
{
    my ( $count ) = (@_);
    my %USERS;

    my @lines = getLogfile();
    foreach my $line ( @lines )
    {
	if ( $line =~ /([^ ]+) -/ )
	{
	    my $entry = $1;
            #    print "Found user $entry\n";
	    $USERS{ $entry } ++;
	}
    }

    if ( ! $SHOW_PLAIN )
    {
	print "<TR><TD><B>IP Address</B> (<B>Hostname</B>)";
	print "<TD><B>Connection Count</B></TD></TR>\n";
    }
    else
    {
	print "IP Address (Hostname)\t\t\tConnection Count\n";
    }


    my $open = "";
    my $mid  = "\t\t";
    my $close = "";

    if ( ! $SHOW_PLAIN )
    {
	$open = "<TR><TD>";
	$mid  = "</TD><TD>";
	$close = "</TD></TR>";
    }

    foreach my $song (sort { $USERS{$b} <=> $USERS{$a} }
		            keys %USERS) 
    {
	#
	#  Only get the hostnames for the clients that we're going
	# to display.
	#
	my $host = ipToName( $song );

	$count -= 1;
	print $open . $song . " (" . $host . ")" . $mid . $USERS{$song} . $close . "\n";
	return if $ count le 0;
    }
}


#
#  Is 'always_stream' enabled in the configuration file?
#
sub alwaysStream()
{
  open( CONFIG, "<" . $DEFAULT_CONFIG ) 
      or die "Cannot open configuration file $DEFAULT_CONFIG : $!";

  my @config = <CONFIG>;
  close( CONFIG );


  my $alwaysStream = 0;

  foreach my $line ( @config )
  {
      next if $line =~ /^\s*#/ ;
      
      if ( $line =~ /always_stream\s*=\s*(\S*)/ )
      {
	  $alwaysStream = $1;
      }
  }

  return( $alwaysStream );
}



#
#
#  Print the top N most popular tracks from the given
# logfile.
#
sub getLogfile()
{

  open( CONFIG, "<" . $DEFAULT_CONFIG ) 
      or die "Cannot open configuration file $DEFAULT_CONFIG : $!";

  my @config = <CONFIG>;
  close( CONFIG );


  my $logFile = undef;

  foreach my $line ( @config )
  {
      next if $line =~ /^\s*#/ ;
      
      if ( $line =~ /logfile\s*=\s*(\S*)/ )
      {
	  #
	  # We're only going to care about the first
	  # logfile line we find.
	  #
	  if ( ! defined( $logFile ) )
	  {
	      $logFile = $1;
	  }
      }
  }

  die "Can't find logfile entry in config file" unless defined $logFile;
  
  open( LOGS, "<" . $logFile ) or die "Cannot open logfile $logFile : $!";
  my @logs = <LOGS>;
  close( LOGS );

  return( @logs );
}


#
#  Parse the command line options.
#
sub parseArguments()
{
    GetOptions(
               "help", \$SHOW_HELP,
               "version", \$SHOW_VERSION,
	       "songs", \$SHOW_SONGS,
	       "dirs", \$SHOW_DIRS,
	       "search", \$SHOW_SEARCH,
	       "users", \$SHOW_USERS,
	       "count=s", \$SHOW_NUMBER,
	       "config=s", \$DEFAULT_CONFIG,
	       "plain", \$SHOW_PLAIN,
	       "agents", \$SHOW_AGENTS
               );

}


#  Show help for this script.
#
sub showHelp()
{
    showVersion();
    print <<END_OF_USAGE;

Usage: mp3d-top options 

Options:
    --agents      Show the top N user agents.
    --songs       Show the top N songs.
    --dirs        Show the top N directories.
    --users       Show the top N users.
    --search      Show the top N search terms.
    --count=N     Set the number of entries to show.
    --config=file Set the GNUMP3d config file.
    --version     Show the version number.
    --help        Show this help.

    --plain       Show output as plain text
END_OF_USAGE
}


#  Show the version number.
#
sub showVersion()
{
    my $revision = $VERSION_NUMBER;

    #
    # Extract the version from the CVS revision marker,
    # the only tricky bit is making sure the words "$" Revision " $"
    # don't apear here - because they'd be replaced - this
    # has confused me before.
    #
    if (  $VERSION_NUMBER =~ /\$([a-zA-Z:]+) ([0-9\.]+) \$/ )
    {
        $revision = $2;
    }

    print "mp3d-top - version $revision - http://www.gnump3d.org\n";

}


#
#  Normalize a given path, removing duplication "/"'s.
#
sub removeDuplicateSlashes($)
{
    my ( $string ) = (@_);

    while ( $string =~ /(.*)\/\/(.*)/ )
    {
	$string = $1 . "/" . $2;
    }

    return( $string );

}

#
#  Convert a given IP address to a hostname.
#
sub ipToName($)
{
    my( $ip ) = ( @_ );
    
    my @address = gethostbyaddr( pack( 'C4', split(/\./, $ip)), 2 );
    return(  @address > 0 ? $address[0] : $ip );
}
