#!/usr/bin/perl

# Distmp3, a program for distributing the encoding of music among several computers.
# Copyright (C) 2000  Martin Josefsson
#
# 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.
#
#
# Author: Martin Josefsson <gandalf@wlug.westbo.se>
#

use IO::Socket;
use IO::Handle;
use Net::hostent;

$|=1;

#----------------------------
$version = "0.1.9";

$config_file = "/etc/distmp3/distmp3.conf";

$RTR = "hejhopp_rtr";
$RTS = "hejhopp_rts";
$ACK = "hejhopp_ack";

$SRV_ID = "hejhopp_server_id";
$CLI_ID = "hejhopp_client_id";

#----------------------------

$SIG{INT} = sub { closedown("Caught INT signal, going down in flames..."); };

$SIG{TERM} = sub { closedown("Caught TERM signal, going up in smoke..."); };

sub parse_config_file
{
	open(CONFIG,"<$_[0]") or die "Couldn't open config file $_[0]";
	while ( <CONFIG> ) {
		if ( !(/^\n/ || /^#/ || /^;/) ) {
			if ( /^SERVER_/ ) {
				chomp;
				($config_option,$config_value) = split("=",$_,2);
				$config_option =~ s/SERVER_//;
				@config{$config_option} = $config_value;
			}
		}
	}
	close CONFIG;

	if ( @config{PROGRAM} ) {
		@config{PROGRAM} =~ s/\$mp3fifo/@config{MP3FIFO}/g;
		@config{PROGRAM} =~ s/\$wavfifo/@config{WAVFIFO}/g;
		@config{PROGRAM} =~ s/[\",\']//g;
	}
}

sub parse_command_line
{
	$row = -1;
LOOP:	while ( ++$row <= @ARGV ) {

		if ( $ARGV[$row] eq "-h" || $ARGV[$row] eq "--help" ) {
			print_usage();
			exit(0);
		}

		if ( $ARGV[$row] eq "-p" ) {
			++$row;
			@config{PORT} = $ARGV[$row];
			next LOOP;
		}

		if ( $ARGV[$row] eq "-s" ) {
			++$row;
			@config{DATASIZE} = $ARGV[$row];
			next LOOP;
		}

		if ( $ARGV[$row] eq "-d" ) {
			++$row;
			if ( $ARGV[$row] == 1 ) {
				@config{DEBUG} = 1;
			} else {
				undef @config{DEBUG};
			}
			next LOOP;
		}

		if ( $ARGV[$row] eq "-df" ) {
			++$row;
			@config{DEBUGFILE} = $ARGV[$row];
			next LOOP;
		}

		if ( $ARGV[$row] eq "-w" ) {
			++$row;
			@config{WAVFIFO} = $ARGV[$row];
			next LOOP;
		}

		if ( $ARGV[$row] eq "-m" ) {
			++$row;
			@config{MP3FIFO} = $ARGV[$row];
			next LOOP;
		}

		if ( $ARGV[$row] eq "-e" ) {
			++$row;
			@config{PROGRAM} = $ARGV[$row];
			@config{PROGRAM} =~ s/\$mp3fifo/@config{MP3FIFO}/g;
			@config{PROGRAM} =~ s/\$wavfifo/@config{WAVFIFO}/g;
			@config{PROGRAM} =~ s/[\",\']//g;
			next LOOP;
		}
	}
}

sub print_usage
{
	print "Usage:  $0 [-p port] [-s datasize] [-d debug] [-df debugfile] [-w wavfifo] [-m mp3fifo] [-e program]\n";
}

sub open_local_connection
{
	printd('Opening local Socket');
	$local = IO::Socket::INET->new(Proto     => "tcp",
			  	       LocalPort => @config{PORT},
				       Listen    => SOMAXCONN,
				       Reuse     => 1)
		or die "Can't bind port @config{PORT} on localhost: $!";
	$local->autoflush(1);
	printd("Port @config{PORT} on localhost ready for connections");
}

sub check_fifo
{
	printd("creating mp3_fifo @config{MP3FIFO}");
	unlink @config{MP3FIFO};
	system('mknod', '-m 0600', @config{MP3FIFO}, 'p') && die "can't mknod @config{MP3FIFO}: $!";

	printd("creating wav_fifo @config{WAVFIFO}");
	unlink @config{WAVFIFO};
	system('mknod', '-m 0600', @config{WAVFIFO}, 'p') && die "can't mknod @config{WAVFIFO}: $!";
}

sub run_program
{
	if ( @config{DEBUG} ) {
		$extra_param = "&";
	}
	else
	{
		$extra_param = "&>/dev/null &";
	}
	system "@config{PROGRAM} $extra_param";
	if ( $? ne 0 ) {
		die "Couldn't run program @config{PROGRAM}";
	}
	printd("Executed program @config{PROGRAM}");
}

sub open_debug_file
{
	open (DEBUG, ">>@config{DEBUGFILE}") or die "Couldn't open debug file @config{DEBUGFILE}";
}

sub close_debug_file
{
	close DEBUG;
}

sub open_wav_fifo
{
	printd('opening wav-fifo');
	open (WAV_FIFO, "> @config{WAVFIFO}") || die "Can't open fifo @config{WAVFIFO} for writing: $!";
	WAV_FIFO->autoflush(1);
}

sub open_mp3_fifo
{
	printd('opening mp3-fifo');
	open (MP3_FIFO, "< @config{MP3FIFO}") || die "Can't open fifo @config{MP3FIFO} for reading: $!";
	MP3_FIFO->autoflush(1);
}

sub close_wav_fifo
{
	printd('closing wav-fifo');
	close WAV_FIFO;
}

sub close_mp3_fifo
{
	printd('closing mp3-fifo');
	close MP3_FIFO;
}

sub send_data
{
	$data = $_[0];
	print $remote_client $data;
}

sub recieve_data
{
	read $remote_client,$rdata,@config{DATASIZE};
	return $rdata;
} 

sub read_from_mp3
{
	read MP3_FIFO,$wdata,@config{DATASIZE};
	return $wdata;
}

sub write_to_wav
{
	$mdata = $_[0];
	print WAV_FIFO $mdata;
}

sub handshake
{
	printd('initiate handshake');

	printd('waiting for CLI_ID');
	read $remote_client,$answer,length($CLI_ID);
	printd('got CLI_ID');
	if ( $answer ne $CLI_ID ) {
		$cli_error = 1;
	}
	if ( $cli_error eq 1 ) {
		printd("Client isn't who he should be");
		$cli_error = 0;
	}
	else
	{
		$cli_error = 0;
		printd('Ok, Client id is what it should be');
	
		send_data($SRV_ID);
		printd('Sent SRV_ID');
	
		printd('Waiting for ACK');
		read $remote_client,$answer,length($ACK);
		printd('got ACK');
		if ( $answer ne $ACK ) {
			$ack_error = 1;
		}
		if ( $ack_error eq 1 ) {
			printd("Ack isn't Ack");
			$ack_error = 0;
		}
		else
		{
			$ack_error = 0;
			printd('ACK ok');
			return "1";
		}
	}
}

sub printd
{
	if ( @config{DEBUG} ) {
		$message = $_[0];
		print "$message\n";
	}
	if ( @config{DEBUGFILE} ) {
		$message = $_[0];
		print DEBUG "$message\n";
	}
}


sub closedown
{
	if ( @config{DEBUG} || @config{DEBUGFILE} ) {
		printd("$_[0]\n");
	}
	if ( @config{DEBUGFILE} ) {
		close DEBUG;
	}
	print "Exited gracefully...\n"; 
	shutdown $local,2;
	close WAV_FIFO;
	close MP3_FIFO;
	unlink @config{MP3FIFO};
	unlink @config{WAVFIFO};
	exit(1); 
};

#Main program

parse_config_file($config_file);

parse_command_line();

if ( @config{DEBUGFILE} ) {
	open_debug_file();
}
printd("Distmp3 daemon version $version");

check_fifo();

open_local_connection();

while ($remote_client = $local->accept()) {
	$remote_client->autoflush(1);
	if ( $hostinfo = gethostbyaddr($remote_client->peeraddr) ) { 
		$remote_host = $hostinfo->name; 
	}
	else
	{
		$remote_host = $remote_client->peerhost;
	}

	printd("Connect from $remote_host");

	if ( handshake() ) { 

		run_program();

		printd('Forking like hell');

		unless ($pid = fork) {
			printd('forking child for retrieving data from the client and sending it to the fifo');
			send_data($RTR);
			printd('Sent RTR');
			open_wav_fifo();
			while ( $data2 = recieve_data() ) {
				write_to_wav($data2);
			}
			close_wav_fifo();
			exit(0);
		}	

		printd('entering code to read data from the fifo and sending it to the client');
		send_data($RTS);
		printd('Sent RTS');
		open_mp3_fifo();
		while ( $data = read_from_mp3() ) {
			send_data($data);
		}
		close_mp3_fifo();
		shutdown $remote_client,1;

		waitpid($pid,0);
	}
printd("Connection from $remote_host terminated");
}
