#!/usr/bin/perl

# randomsig -  A program that picks a random line from one or more files for
#              for use as an email signature.
#
# Copyright (C) 2000 Suso Banderas

# 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.
#
# 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
# You may contact the author at <suso@suso.org>.


# Description:  randomsig is a program that, when run, will create a .signature file
#               in your home directory that is either a named pipe or if the program
#               is run in safe mode the .signature file will be updated every so
#               often.  The idea that each time you read from the named pipe or the
#               file is updated the signature will be changed.


################
# MAIN PROGRAM #
################

$version = '1.9';
use Getopt::Std;
use File::Copy;

# If the user doesn't already have a config file, we want to help them out by
# copying one for them from the distrobution.
my $user_homedir = (getpwuid($<))[7];
if (!-e "$user_homedir/.randomsigrc") {
	print "No .randomsigrc file found in $user_homedir. Trying to creating one.\n";
	if (-e '/usr/local/etc/randomsig/.randomsigrc') {
		copy('/usr/local/etc/randomsig/.randomsigrc', "$user_homedir/.randomsigrc") ||
			die "Can't create .randomsigrc file: $!\n";
	} else {
		print "No example .randomsigrc file found, please use the example .randomsigrc\nfrom the distrobution you downloaded.\n";
		exit(1);
	}
	print "Now, edit the .randomsigrc file just created in your home directory and then re-run randomsig.\n";
	exit(0);
}


getopts('hvlfsV');
if ($opt_h || $opt_v) { &usage; exit(0); }

# See the subroutines section for an explaination
# on why there are here.
$SIG{PIPE}	= 'signal_pipe';     # pine sends these sometimes. :-(
$SIG{TERM}	= 'signal_handler';  # A normal kill command sends this.
$SIG{INT}	= 'signal_handler';  # Ctrl-C from the keyboard.
$SIG{HUP}	= 'read_config';     # This will happen after a while anyways.

chdir;  # go home
$| = 1;

# All configuration is now done in a .randomsigrc file
# in the user's home directory.
read_config();


srand;

# Before we fork or anything else let's see if another randomsig
# process is running in the place where we will.  NOTE: Right now
# this only checks for a named pipe file called. signature.  In a
# future version it should probably also make sure there is really
# a process attached to it.
unless ($opt_l) {
	if (-p $SIGFILE) {
		print "\nERROR: Another randomsig named pipe called $SIGFILE file is already in place.\nExiting\n";
		exit(1);
	}
}
# We're going to try forking away from the shell so
# that the user doesn't have to know about process
# controll.  Unless they specify the -f option,
# let's fork baby.

if (defined($opt_f) || defined($opt_l)) {
	print "Running main program non-forked\n" if ($opt_V);
	&main_program();
} else {
	FORK: {
		if ($pid = fork) { # parent
			exit(0);
		} elsif (defined $pid) { # child
			print "Forked to PID $pid\n" if ($opt_V);
			&main_program();
		} elsif ($! =~ /No more process/) {
			print "Sleeping 5 seconds because can't fork\n" if ($opt_V);
			sleep 5;
			redo FORK;
		} else {
			die "Can't fork: $!\n";
		}
	}
}

# I normally wouldn't write the main part of the
# program as a subroutine but I've never wrote 
# a REAL program that's forked away from the shell
# before so I have to get used to it. :-)
sub main_program () {

	# This will give each file an initial time last read
	# value and the global sig hashes.  We want it to be
	# 0 and empty for each one at this time.
	foreach $file (@FILES, @QUOTES) {
		print "Setting $file lastread time to 0\n" if ($opt_V);
		$sig_file_lastread{$file} = 0;
	}
	
	#******************************************************************************
	# This first loop is the outside loop.  It gets read when the program starts
	# and creates the array that holds all the valid lines.  It will get reread
	# after the first read from the pipe after $time seconds has passed.  This
	# keeps your random lines array up to date with what's in the files.
	#
	READ: while (1) {
	
		if (!defined($lasttime)) { $lasttime = 0; }

		if (defined($CANCELFILE) && -f $CANCELFILE) {
			open (CANCEL, "$CANCELFILE");
			print "Reading in cancelfile: $CANCELFILE\n" if ($opt_V);
			while(<CANCEL>) {
				$cancelline = $_;
				chomp($cancelline);
				unless (grep(/^#/,$cancelline)) {
					push(@cancel_array, $cancelline);
				}
			}
			close(CANCEL);
		}
	

		if (defined(@FILES)) {	
			foreach $file (@FILES) {
				# if the file has changed since
				# the last time we read things in
				# then read in that file.
				if (-f $file && ($sig_file_lastread{$file} < (stat($file))[9])) {
					print "Reading $file as non-quote file\n" if ($opt_V);
					open (FILE, $file);	# Just continue so that there
					%$file = ();		# are no problems if the file
								# is empty, etc.
		
					#**************************************************************
					# This is the loop that goes through the file $file
					# and checks lines for validity.  Unless you're just weird
					# you probably don't want every line to be considered valid,
					# Especially if your files are Mailbox format. I've found that
					# searching for lines with punctuation at the end works well.
					# In the future I may make this configurable.
					#
					LINE: while(<FILE>) {
						my $line = $_;
						chomp($line);
						if (grep(/$MAIN_EXPRESSION/, $line)) {
							foreach $expression (@cancel_array) {
								if (grep(/$expression/, $line)) {
									next LINE;
								}
							}
							$$file{$line}++;
						}
					}
					close(FILE);
					$sig_file_lastread{$file} = time();
					print "Set last read time for $file to $sig_file_lastread{$file}\n" if ($opt_V);
				}
			}
		}
	
		#*********************************************************
		# If you have a special file with lines that you want
		# included in the line array no matter what this loop
		# will read them in.  Just add them to the @quotes array
		# at the top of the program.
		#
		foreach $quotefile (@QUOTES) {
			if (-f $quotefile && ($sig_file_lastread{$quotefile} < (stat($quotefile))[9])) {
				%$quotefile = ();
				print "Reading in $quotefile as a quoted file\n" if ($opt_V);
				open (QUOTES, $quotefile);
				while (<QUOTES>) {
					my $line = $_;
					chomp($line);
					$$quotefile{$line}++;
				}
				close(QUOTES);
				$sig_file_lastread{$quotefile} = time();
			}
		}
	
		# Make a sorted array of the lines that qualify.
		foreach $file (@FILES, @QUOTES) {
			print "Adding qualifying lines from $file to global lines array\n" if ($opt_V);
			push(@line_array, keys(%$file));
		}
		$numlines = scalar(@line_array);
		print "$numlines total qualifying lines\n" if ($opt_V);
	
	
	        #*************************************************************************
	        # This is the inside loop that continuously runs, waiting for you to
	        # read from the .signature file.  If a certain amount of time has passed
	        # it will back once to the outter loop.
	        #
		if($opt_l) {        # Just list the qualifying lines and exit.
				%outputhash = ();
				print "\nPreparing to print out all qualifying lines\n" if ($opt_V);
				foreach $file (@FILES, @QUOTES) {
					foreach $line (keys(%$file)) {
						$outputhash{$line} = $$file{$line};
					}
				}
				@linearray = sort keys(%outputhash);
				foreach $line (@linearray) {
					print $outputhash{$line}, ":$line\n";
				}
				exit(0);
		} else {
			while (1) {
				unless ($opt_s) {
					unless (-p $SIGFILE) {     # just listing out the lines or the pipe is 
						print "Creating $SIGFILE as named pipe\n" if ($opt_V);

						# Just in case there is already one there.
						if (-e $SIGFILE) {
							unlink($SIGFILE) or die "Can't unlink previous sigfile $SIGFILE: $!\n";
						}

						# System return values are backwards from what you'd expect so you have
						# to use an and instead of an or.
						system('mknod', $SIGFILE, 'p') and die "Could not mknod $SIGFILE: $!\n";
					}
				}
				# This random number will be used to determine which line to use
				# out of all the lines read in from all the files.
				$random = int (rand($numlines));
				print "Random number: $random\n" if ($opt_V);
			
				$randomline = $line_array[$random];
				chomp($randomline);
				print "Random line: $randomline\n" if ($opt_V);
		
				open (SIGNATURE, "> $SIGFILE") or die "can't write $SIGFILE: $!";
	
				if (defined($MUTATION_FROM) && defined($MUTATION_TO)) {
					print "Mutating randomline using user specified MUTATION variables\n" if ($opt_V);
					$randomline =~ s/$MUTATION_FROM/$MUTATION_TO/g;
					print "Post mutation randomline: $randomline\n" if ($opt_V);
				}
				# Wrap the line if it's longer than $wrap_at chars.
				if (defined($WRAP_AT) && (length($randomline) > $WRAP_AT)) {
					print "Wrapping randomline at $WRAP_AT characters\n" if ($opt_V);
					$randomline =~ s/(.{$WRAP_AT}[\S]*)\s/$1\n/g;
					chomp($randomline);
				}

				if (-e $SIGREAD) {	
					print "Opening signature read in file: $SIGREAD\n" if ($opt_V);
					open(SIGREAD, "$SIGREAD") || warn "Cannot read $SIGREAD template signature file: $!\n";
					print SIGNATURE "-- \n";
					$n = 0;
					OUT: while (<SIGREAD>) {
						if (grep(/^--[ ]{0,1}$/, $_) && $n == 0) {
							print "Already a double-dash-space line, skipping this line\n" if ($opt_V);
							next OUT;
						}
						$_ =~ s/\$randomline/$randomline/g;
						print SIGNATURE "$_";
						$n++;
					}
					close(SIGREAD);
				} else {
					print "No SIGREAD template file($SIGREAD), printing default.\n" if ($opt_V);
					print SIGNATURE "-- \n$randomline\n";
				}

				close (SIGNATURE);
				print "Finished writing to signature file\n" if ($opt_V);
				if ($opt_s) {
					print "Safe mode: sleeping " . caclulate_time($UPDATE_TIME) . " or 1800 seconds\n" if ($opt_V);
					sleep (calculate_time($UPDATE_TIME) || 1800);
				} else {
					print "Pipe mode: Sleeping 1 second\n" if ($opt_V);
					sleep 1;  # Don't change this value to anything less than 1.
				}

				#*****************************************************
				# This piece of code is so that we're not always  
				# checking the files to see if they've changed. The
				# $reread_time variable determines how often we
				# check to see if any of the files have been updated.
				#
				if ((time() - $lasttime) > (calculate_time($REREAD_TIME) || 1800)) {
					print "wait time is up, going back to re-read files\n" if ($opt_V);
					next READ;
				}
			}
		}
	}
}




###############
# SUBROUTINES #
###############

sub usage {
	print <<EOF;
-----------------------
 randomsig version $version
-----------------------
 Description:  Program that will pull a random line
               out of some files and send it to a
               named pipe for reading.
               See top of program file for details.

 Usage:
        $ randomsig
        $ randomsig [options]

 Options:
           -h -v  -- Show this message
           -V     -- Turn on extra verbosity.
           -l     -- Only print out all qualifying
                     lines and exit.
           -f     -- Don't fork away from the shell.
           -s     -- Safe mode.  Don't create a named
                     pipe.  Just fork to the background
                     and update the signature file every
                     so often as specified by \$update_time.
 
EOF
} 

#****************************************
# If for some reason the program
# dies we don't want to leave the named
# pipe file behind because if another
# program reads it that program might
# freeze up.  So we have our own signal
# handler.

sub read_config {
	$signal = shift;
	my $user_homedir = (getpwuid($<))[7];
	my $randomsig_config_file = "$user_homedir/.randomsigrc";
	print "Reading config\n" if ($opt_V);
	do $randomsig_config_file;
	if ($signal) {
		print "\nSuccessfully re-read the randomsig config.\n" if ($opt_V);
	}
}

# based on certain strings, we should allow the user
# to use m for minutes, h for hours, etc.
sub calculate_time {
	my $string = shift;

	$string = shift;
	$_ = $string;
	my $myseconds = 0;

	# weeks.
	if (/([0-9\.]+)[ ]*w/ig) {
		$myseconds += ($1 * 604800);
	}

	# days.
	if (/([0-9\.]+)[ ]*[(d|day|days)]/) {
		$myseconds += ($1 * 86400);
	}

	# hours.
	if (/([0-9\.]+)[ ]*[(h|hour|hours)]/) {
		$myseconds += ($1 * 3600);
	}

	# minutes.
	if (/([0-9\.]+)[ ]*[(m|min|minute|minutes)]/) {
		$myseconds += ($1 * 60);
	}

	# seconds.
	if (/([0-9]+)[ ]*[(s|sec|second|seconds|)]$/) {
		$myseconds += $1;
	}
	$myseconds =~ s/\..*$//;
	return $myseconds;
}

sub signal_handler {
	$signal = shift;
	print STDERR "\nGot a SIG $signal! Cleaning up and exiting.\n" if ($opt_V);
	unless ($opt_s) {
		unlink($SIGFILE);
	}
	exit(1);
}

# Because Pine is a wank, we have to give SIGPIPE it's own handler
# in an attempt to fix problems with it reading improperly from
# the signature file when it's a named pipe.
sub signal_pipe {
	$sig = shift;
	print STDERR "\nGot a SIG $signal! Sleeping 1/4 second.\nIf you're running pine, try restarting it.\n\n" if ($opt_V);
	select(undef,undef,undef,0.05);
}

#--end--
