#!/usr/bin/perl -w
use strict;

# jukebox - http://raf.org/jukebox/
#
# Copyright (C) 2002 raf <raf@raf.org>
#
# 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
# or visit http://www.gnu.org/copyleft/gpl.html

# jukeboxc - command line jukebox network client
#
# 20021208 raf <raf@raf.org>

# Set defaults and load configuration

my ($name) = $0 =~ /([^\/]+)$/;
my $default_host = 'jukebox';
my $default_port = 1221;
my $default_opts = 'short';

load('/etc/jukebox.conf');
load('$ENV{HOME}/.jukeboxrc');

sub help
{
	print << "ENDHELP";
NAME

$name - command line jukebox network client

SYNOPSIS

  $name [options] [--] [.|selector...]
  options:
    -h, --help          - Show the help message then exit
    -V, --version       - Show the version message then exit
    -d, --debug         - Print debug messages
    -g, --gui           - Launch graphical jukebox client ($name.jar)
    -a, --all           - Select everything (not just your favourites)
    -j, --strict        - Search for selectors only in "Jukebox" tags
    -m, --mandatory     - Search treating all selectors as mandatory
    -n, --list          - Just list matching tracks (don't play them)
    -x, --extra         - Show artist/album/title as well (implies -n)
    -e, --elide         - Suppress duplicate artist/albums (implies -x)
    -v, --withtags      - Include tags when listing tracks (implies -x)
    -s, --sequential    - Play tracks sequentially, then stop
    -c, --continuous    - Play continuously (even if -s)
    -f, --playlist file - Play tracks from a playlist file
    -T, --tagstats      - List known selectors/categories/tags
    -0, --stop          - Stop the jukebox
    -H, --host host     - Override default jukebox hostname ($default_host)
    -P, --port port     - Override default jukebox tcp port ($default_port)

DESCRIPTION

$name is a jukebox network client that can either operate as a command
line utility or it can launch the graphical jukebox client (jukeboxc.jar).
The -g option launches the graphical jukebox client. For more details,
see "man jukeboxc.jar".

Without the -g option, $name is a command line jukebox network client with
the same functionality as the graphical version. Many of jukebox's options
are accepted and mean the same thing except that they operate via the
jukebox server instead of directly on a local wav/mp3/ogg/flac collection.
For more details, see "man jukebox".

The -0 option stops the jukebox server playing music. The jukebox server
can also be stopped by using "." as the only selector/argument.

The -H and -P options override the host and port to connect to.
These (and -h) are the only options that may be used with the -g option.

EXAMPLES

See "man jukebox" for examples.

NOTE

The long options are only available if the value of the juke_opts variable
in /etc/jukebox.conf is "long". This is because the standard perl module,
Getopt::Long, requires the presence of a "--" argument before any arguments
that begin with "+" because it thinks they are options (unless
\$POSIXLY_CORRECT is set in the environment). Since it is expected that this
program will receive many command line arguments that begin with "+", the
default is to only handle single letter options via the Getopt::Std module.
Note that, with either short or long options, "--" must be used whenever
there are search terms that begin with "-" because these really do look like
options.

FILES

  /etc/jukebox.conf - System wide configuration file
  ~/.jukeboxrc      - User specific configuration file

SEE ALSO

  rip(1), riptrack(1), mktoc(1), toc2names(1), toc2tags(1),
  cdr(1), cdrw(1), burn(1), burnw(1), cdbackup(1), mp3backup(1),
  jukebox(1), jukeboxc(1), jukeboxc.jar(1), jukeboxd(8),
  jukeboxd-init.d(8), jukebox.conf(5),
  http://raf.org/jukebox/Jukebox-HOWTO

AUTHOR

raf <raf\@raf.org>

ENDHELP

	exit;
}

sub version()
{
	print "jukeboxc-0.1\n";
	exit;
}

# Check the arguments

my %opt;
my $hostnl = "\n";
my $inetnl = "\015\012";
my $user = $ENV{LOGNAME};
help() unless do_opts();
my $host = $opt{H} || $default_host;
my $port = $opt{P} || $default_port;
do_gui() if exists $opt{g};
help() if exists $opt{h};
version() if exists $opt{V};
my $debug = exists $opt{d};

my $stop = exists $opt{'0'} || $#ARGV == 0 && defined $ARGV[0] && $ARGV[0] eq '.';
my $list = exists $opt{n} || exists $opt{x} || exists $opt{e} || exists $opt{v} || exists $opt{T};
my $file = exists $opt{f};

die "Abort: Selectors and the -0 option are mutually exclusive.\n" if $stop && ($#ARGV > 0 || defined $ARGV[0] && $ARGV[0] ne '.');
die "Abort: Selectors and the -a option are mutually exclusive.\n" if exists $opt{a} && $#ARGV != -1;
die "Abort: Selectors and the -f option are mutually exclusive.\n" if exists $opt{f} && $#ARGV != -1;
die "Abort: The -a and -j options are mutually exclusive.\n" if exists $opt{a} && exists $opt{j};
die "Abort: The -a and -m options are mutually exclusive.\n" if exists $opt{a} && exists $opt{m};
die "Abort: The -a and -f options are mutually exclusive.\n" if exists $opt{a} && exists $opt{f};
die "Abort: The -f and -j options are mutually exclusive.\n" if exists $opt{f} && exists $opt{j};
die "Abort: The -f and -m options are mutually exclusive.\n" if exists $opt{f} && exists $opt{m};
die "Abort: The -f and -nxevT options are mutually exclusive.\n" if exists $opt{f} && $list;
die "Abort: The -f option's argument must be \"-\" or a filename.\n" if exists $opt{f} && (!defined $opt{f} || ($opt{f} ne '-' && ! -f $opt{f}));
die "Abort: The -T and -nxev options are mutually exclusive.\n" if exists $opt{T} && (exists $opt{n} || exists $opt{x} || exists $opt{e} || exists $opt{v});

# Play, list or stop the music

do_stop() if $stop;
do_list() if $list;
do_file() if $file;
do_start();

# Launch the graphical client

sub do_gui
{
	my ($dir) = $0 =~ /^(.*)\/[^\/]+$/;
	$dir = '.' unless $dir;
	my $args = ((exists $opt{h}) ? '-h' : '') . " $host $port";
	exec("java -jar $dir/jukeboxc.jar $args\n");
	die "Failed to exec graphical jukebox client: $!\n";
}

# Start the music

sub do_start
{
	do_check();
	my $opts = ($opt{a} ? 'a' : '') . ($opt{j} ? 'j' : '') . ($opt{m} ? 'm' : '') . ($opt{s} ? 's' : '') || ($opt{c} ? 'c' : '');
	my $crit = join ' ', map(/\s/ ? "'$_'" : $_, @ARGV);
	do_action("user:$user command:start options:$opts criteria:$crit");
}

# Stop the music

sub do_stop
{
	do_action("user:$user command:stop");
}

# Obtain and print a playlist from the jukebox server

sub do_list
{
	do_check();
	my $opts = ($opt{n} ? 'n' : '') . ($opt{x} ? 'x' : '') . ($opt{e} ? 'e' : '') . ($opt{v} ? 'v' : '') . ($opt{a} ? 'a' : '') . ($opt{j} ? 'j' : '') . ($opt{m} ? 'm' : '') . ($opt{s} ? 's' : '') . ($opt{c} ? 'c' : '') . ($opt{T} ? 'T' : '');
	my $crit = join ' ', map(/\s/ ? "'$_'" : $_, @ARGV);
	do_action("user:$user command:list options:$opts criteria:$crit");
}

# Send a playlist to the jukbox server

sub do_file
{
	my $playlist = '';
	open(PLAYLIST, $opt{f}) or die "Failed to open '$opt{f}' for reading: $!\n";
	$playlist .= $_ while (<PLAYLIST>);
	close(PLAYLIST);
	$playlist =~ s/$hostnl/$inetnl/g;
	my $opts = 'f' . ($opt{s} ? 's' : '') . ($opt{c} ? 'c' : '');
	do_action("user:$user command:file options:$opts criteria:$inetnl$playlist");
}

# Validate selection criteria (no funny characters)

sub do_check
{
	die "Invalid selection criteria: @ARGV\n" if "@ARGV" !~ /^[\d\sa-zA-Z'\",.?!+-]*$/;
}

# Engage the jukebox server and print its reply

sub do_action
{
	my ($msg) = @_;
	print "<<<$msg\n" if $debug;
	$msg .= $inetnl;

	use Socket;
	$port = getservbyname($port, 'tcp') unless $port =~ /^\d+$/;
	die "Invalid port: '$port'\n" unless $port > 0 && $port <= 65535;
	my $iaddr = inet_aton($host) || die "Invalid address: '$host'\n";
	my $paddr = sockaddr_in($port, $iaddr);
	my $proto = getprotobyname('tcp');
	socket(SERVER, PF_INET, SOCK_STREAM, $proto) || die "socket failed: $!\n";
	connect(SERVER, $paddr) || die "connect failed: $!\n";

	my $length = length($msg);
	my $offset = 0;
	while ($length)
	{
		my $bytes = syswrite(SERVER, $msg, $length, $offset);
		die "syswrite failed: $!\n" if $bytes <= 0;
		$length -= $bytes;
		$offset += $bytes;
	}

	vec(my $rin = '', fileno(SERVER), 1) = 1;

	my $reply = '';

	for ($length = 0;;)
	{
		warn("Request timed out\n"), last unless select(my $rout = $rin, undef, undef, 30);
		my $bytes = sysread(SERVER, $reply, 512, $length);

		last unless $bytes;
		$length += $bytes;
	}

	close(SERVER);
	$reply =~ s/$inetnl/$hostnl/g;
	print $reply;
	exit;
}

# Load the configuration file

sub load
{
	my ($config) = @_;
	return unless -r $config;
	return unless open(CONFIG, $config);

	while (<CONFIG>)
	{
		s/#.*$//;
		s/^\s+//;
		s/\s+$//;
		s/\s+/ /g;
		next if /^$/;
		$default_host = $1 if /^juke_host=['"]?([^'"]+)['"]?$/;
		$default_port = $1 if /^juke_port=['"]?([^'"]+)['"]?$/;
		$default_opts = $1 if /^juke_opts=['"]?([^'"]+)['"]?$/;
	}

	close(CONFIG);
}

# Process the command line options

sub do_opts
{
	if ($default_opts eq 'long')
	{
		use Getopt::Long; #qw(:config gnu_getopt);
		Getopt::Long::Configure qw(no_getopt_compat permute bundling);
		GetOptions \%opt, qw(h|help V|version d|debug g|gui a|all j|strict m|mandatory n|list x|extra e|elide v|withtags s|sequential c|continuous f|playlist=s T|tagstats 0|O|stop H|host=s P|port=s);
	}
	else
	{
		use Getopt::Std;
		getopts 'hVdgajmnxevscf:T0H:P:', \%opt;
	}
}

# vi:set ts=4 sw=4
