/* $Id: command.c,v 1.6 1999/03/10 07:58:02 boyns Exp $ */

/*
 * Copyright (C) 1993-99 Mark R. Boyns <boyns@doit.org>
 *
 * This file is part of rplay.
 *
 * rplay 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.
 *
 * rplay 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 rplay; see the file COPYING.  If not, write to the
 * Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#include "rplayd.h"
#include "command.h"
#include "connection.h"
#include "server.h"
#include "host.h"
#include "spool.h"
#include "cache.h"
#include "sound.h"
#include "misc.h"
#include "strdup.h"
#include "getopt.h"
#ifdef HAVE_HELPERS
#include "helper.h"
#endif /* HAVE_HELPERS */

#undef is_true
#define is_true(string)				\
    (strcmp (string, "true") == 0		\
     || strcmp (string, "t") == 0		\
     || strcmp (string, "1") == 0		\
     || strcmp (string, "yes") == 0		\
     || strcmp (string, "y") == 0		\
     || strcmp (string, "on") == 0)		\

typedef struct
{
    char *name;
    int min_args;
    int max_args;
    char *usage;
#ifdef __STDC__
    int (*func) (CONNECTION *c, int argc, char **argv);
#else
    int (*func) ( /* CONNECTION *c, int argc, char **argv */ );
#endif
}
COMMAND;

#ifdef __STDC__
static int do_command(CONNECTION *c, int argc, char **argv);
static int command_quit(CONNECTION *c, int argc, char **argv);
static int command_unknown(CONNECTION *c, int argc, char **argv);
static int command_help(CONNECTION *c, int argc, char **argv);
static int command_get(CONNECTION *c, int argc, char **argv);
static int command_put(CONNECTION *c, int argc, char **argv);
static int command_list(CONNECTION *c, int argc, char **argv);
static int command_find(CONNECTION *c, int argc, char **argv);
static int command_execute(CONNECTION *c, int argc, char **argv);
static int command_access(CONNECTION *c, int argc, char **argv);
static int command_volume(CONNECTION *c, int argc, char **argv);
static int command_info(CONNECTION *c, int argc, char **argv);
static int command_version(CONNECTION *c, int argc, char **argv);
static int command_wait(CONNECTION *c, int argc, char **argv);
static int do_execute(CONNECTION *c, int argc, char **argv);
static int command_status(CONNECTION *c, int argc, char **argv);
static int command_application(CONNECTION *c, int argc, char **argv);
static int command_reset(CONNECTION *c, int argc, char **argv);
static int command_skip(CONNECTION *c, int argc, char **argv);
static int command_set(CONNECTION *c, int argc, char **argv);
static int command_modify(CONNECTION *c, int argc, char **argv);
static int command_monitor(CONNECTION *c, int argc, char **argv);
#else
static int do_command( /* CONNECTION *c, int argc, char **argv */ );
static int command_quit( /* CONNECTION *c, int argc, char **argv */ );
static int command_unknown( /* CONNECTION *c, int argc, char **argv */ );
static int command_help( /* CONNECTION *c, int argc, char **argv */ );
static int command_get( /* CONNECTION *c, int argc, char **argv */ );
static int command_put( /* CONNECTION *c, int argc, char **argv */ );
static int command_list( /* CONNECTION *c, int argc, char **argv */ );
static int command_find( /* CONNECTION *c, int argc, char **argv */ );
static int command_execute( /* CONNECTION *c, int argc, char **argv */ );
static int command_access( /* CONNECTION *c, int argc, char **argv */ );
static int command_volume( /* CONNECTION *c, int argc, char **argv */ );
static int command_info( /* CONNECTION *c, int argc, char **argv */ );
static int command_version( /* CONNECTION *c, int argc, char **argv */ );
static int command_wait( /* CONNECTION *c, int argc, char **argv */ );
static int do_execute( /* CONNECTION *c, int argc, char **argv */ );
static int command_status( /* CONNECTION *c, int argc, char **argv */ );
static int command_application( /* CONNECTION *c, int argc, char **argv */ );
static int command_reset( /* CONNECTION *c, int argc, char **argv */ );
static int command_skip( /* CONNECTION *c, int argc, char **argv */ );
static int command_set( /* CONNECTION *c, int argc, char **argv */ );
static int command_modify( /* CONNECTION *c, int argc, char **argv */ );
static int command_monitor( /* CONNECTION *c, int argc, char **argv */ );
#endif

#ifdef DEBUG
extern int command_die( /* CONNECTION *c, int argc, char **argv */ );
#endif

static COMMAND commands[] =
{
/*      
 *    command         min     max     usage                   function
 */
    {"access", -1, -1, "", command_access},
    {"application", 1, -1, "name", command_application},
    {"continue", 1, -1, "id|sound ...", command_execute},
#ifdef DEBUG
    {"die", -1, -1, "", command_die},
#endif
    {"done", 1, -1, "id|sound ...", command_execute},
    {"find", 1, 1, "sound", command_find},
    {"get", 1, 1, "sound", command_get},
    {"help", -1, -1, "", command_help},
    {"info", 1, 1, "sound", command_info},
#ifdef AUTH
    {"list", 0, 1, "[connections|hosts|servers|spool|sounds]", command_list},
#else
    {"list", 0, 1, "[connections|servers|sounds}", command_list},
#endif				/* AUTH */
    {"modify", 2, -1, "id [count|list-count|priority|sample-rate|volume] ...", command_modify},
    {"monitor", -1, -1, "", command_monitor},
    {"pause", 1, -1, "id|sound ...", command_execute},
    {"play", 1, -1, "sound ...", command_execute},
    {"put", 2, -1, "id|sound size", command_put},
    {"quit", 0, 0, "", command_quit},
    {"reset", 0, 0, "", command_reset},
    {"set", 1, -1, "name[=value] ...", command_set},
    {"skip", 1, -1, "id count", command_skip},
    {"status", 0, 0, "", command_status},
    {"stop", 1, -1, "id|sound ...", command_execute},
    {"version", 0, 0, "", command_version},
    {"volume", 0, 1, "[[+|-]volume]", command_volume},
    {"wait", -1, -1, "id|event|RPTP command", command_wait},
};
#define NCOMMANDS	(sizeof(commands)/sizeof(COMMAND))

/* Copy of the current RPTP command.  */
static char command_buffer[RPTP_MAX_LINE];

static char *default_client_data = "";

#ifdef __STDC__
int
command(CONNECTION *c, char *buf)
#else
int
command(c, buf)
    CONNECTION *c;
    char *buf;
#endif
{
    char *argv[RPTP_MAX_ARGS], *p;
    int argc = 0, first = 1;

    report(REPORT_INFO, "%s command=\"%s\"\n", inet_ntoa(c->sin.sin_addr), buf);
    strncpy(command_buffer, buf, sizeof(command_buffer));

    while ((p = strtok(first ? buf : NULL, " \t")))
    {
	argv[argc++] = p;
	first = 0;
	if (argc == RPTP_MAX_ARGS - 1)
	{
	    break;
	}
    }
    argv[argc] = NULL;

    if (argc == 0)
    {
	return 0;
    }
    else
    {
	return do_command(c, argc, argv);
    }
}

#ifdef __STDC__
static int
do_command(CONNECTION *c, int argc, char **argv)
#else
static int
do_command(c, argc, argv)
    CONNECTION *c;
    int argc;
    char **argv;
#endif
{
    int i;

    for (i = 0; i < NCOMMANDS; i++)
    {
	if (strcmp(commands[i].name, argv[0]) == 0)
	{
	    if ((commands[i].min_args >= 0 && argc - 1 < commands[i].min_args)
		|| (commands[i].max_args >= 0 && argc - 1 > commands[i].max_args))
	    {
		connection_reply(c, "%cerror=\"usage: %s %s\"",
			   RPTP_ERROR, commands[i].name, commands[i].usage);
		return 0;
	    }
	    else
	    {
		return (*commands[i].func) (c, argc, argv);
	    }
	}
    }

    return command_unknown(c, argc, argv);
}

#ifdef __STDC__
static int
command_quit(CONNECTION *c, int argc, char **argv)
#else
static int
command_quit(c, argc, argv)
    CONNECTION *c;
    int argc;
    char **argv;
#endif
{
    /* connection_close (c); */
    return -1;
}

#ifdef __STDC__
static int
command_unknown(CONNECTION *c, int argc, char **argv)
#else
static int
command_unknown(c, argc, argv)
    CONNECTION *c;
    int argc;
    char **argv;
#endif
{
    char *client_data;

    client_data = rptp_parse(command_buffer, "client-data");
    if (!client_data)
    {
	client_data = default_client_data;
    }

    connection_reply(c, "%cerror=\"unknown command `%s'\" command=\"%s\" client-data=\"%s\"",
		     RPTP_ERROR, argv[0], argv[0], client_data);

    return 0;
}

#ifdef __STDC__
static int
command_help(CONNECTION *c, int argc, char **argv)
#else
static int
command_help(c, argc, argv)
    CONNECTION *c;
    int argc;
    char **argv;
#endif
{
    int i;
    char fmt[1024];
    EVENT *e;
    static BUFFER *b;

    if (b == NULL)
    {
	b = buffer_create();
	b->status = BUFFER_KEEP;
	SNPRINTF(SIZE(b->buf, BUFFER_SIZE), "%cmessage=\"command summary\" command=help\r\n", RPTP_OK);
	b->nbytes += strlen(b->buf);
	for (i = 0; i < NCOMMANDS; i++)
	{
	    SNPRINTF(SIZE(fmt, sizeof(fmt)), "%-8s %s\r\n", commands[i].name, commands[i].usage);
	    SNPRINTF(SIZE(b->buf + b->nbytes, BUFFER_SIZE - b->nbytes), fmt);
	    b->nbytes += strlen(fmt);
	}
	SNPRINTF(SIZE(b->buf + b->nbytes, BUFFER_SIZE - b->nbytes), ".\r\n");
	b->nbytes += 3;
    }

    e = event_create(EVENT_WRITE, b);
    event_insert(c, e);

    return 0;
}

#ifdef __STDC__
static int
command_get(CONNECTION *c, int argc, char **argv)
#else
static int
command_get(c, argc, argv)
    CONNECTION *c;
    int argc;
    char **argv;
#endif
{
    SOUND *s;
    EVENT *e;
    char *sound_name = NULL;
    int old_style = 0;
    char *client_data = default_client_data;

    if (strchr(command_buffer, '='))
    {
	sound_name = rptp_parse(command_buffer, "sound");
	client_data = rptp_parse(0, "client-data");
	if (!client_data)
	{
	    client_data = default_client_data;
	}
    }
    if (!sound_name)
    {
	sound_name = argv[1];
	old_style++;
    }

#ifdef AUTH
    if (!host_access(c->sin, HOST_READ))
    {
	report(REPORT_NOTICE, "%s get %s - read access denied\n",
	       inet_ntoa(c->sin.sin_addr),
	       argv[1]);
	connection_reply(c, "%cerror=\"access denied\" command=get client-data=\"%s\"",
			 RPTP_ERROR, client_data);
	return 0;
    }
#endif /* AUTH */

    s = sound_lookup(sound_name, SOUND_DONT_FIND, NULL);
    if (s == NULL || s->status != SOUND_READY)
    {
	report(REPORT_NOTICE, "%s get %s - not found\n",
	       inet_ntoa(c->sin.sin_addr), sound_name);
	connection_reply(c, "%cerror=\"%s not found\" command=get client-data=\"%s\"",
			 RPTP_ERROR, sound_name, client_data);
    }
    else if (s->type != SOUND_FILE)
    {
	connection_reply(c, "%cerror=\"%s not a file\" command=get client-data=\"%s\"",
			 RPTP_ERROR, s->name, client_data);
    }
    else
    {
	e = event_create(EVENT_WRITE_SOUND, s);
	if (e == NULL)
	{
	    report(REPORT_NOTICE, "%s get %s - cannot open\n",
		   inet_ntoa(c->sin.sin_addr), s->name);
	    connection_reply(c, "%cerror=\"cannot open %s\" command=get client-data=\"%s\"",
			     RPTP_ERROR, s->name, client_data);
	}
	else
	{
	    report(REPORT_NOTICE, "%s get %s %d\n",
		   inet_ntoa(c->sin.sin_addr), s->name, s->size);
	    if (old_style)
	    {
		connection_reply(c, "%c%s %d", RPTP_OK, s->name, s->size);
	    }
	    else
	    {
		connection_reply(c, "%csound=\"%s\" size=%d command=get client-data=\"%s\"",
				 RPTP_OK, s->name, s->size, client_data);
	    }
	    event_insert(c, e);
	}
    }

    return 0;
}

#ifdef __STDC__
static int
command_put(CONNECTION *c, int argc, char **argv)
#else
static int
command_put(c, argc, argv)
    CONNECTION *c;
    int argc;
    char **argv;
#endif
{
    EVENT *e;
    int sound_size;
    char *name, *sound_name = NULL, *p;
    SOUND *s;
    int fd;
    int old_style = 0;
    int spool_id = 0;
    char *client_data = default_client_data;

    if (strchr(command_buffer, '='))
    {
	client_data = rptp_parse(command_buffer, "client-data");
	if (!client_data)
	{
	    client_data = default_client_data;
	}
    }
    else
    {
	old_style++;
    }

#ifdef AUTH
    if (!host_access(c->sin, HOST_WRITE))
    {
	report(REPORT_NOTICE, "%s put %s - write access denied\n",
	       inet_ntoa(c->sin.sin_addr),
	       argv[1]);
	connection_reply(c, "%cerror=\"access denied\" command=put client-data=\"%s\"",
			 RPTP_ERROR, client_data);
	return 0;
    }
#endif /* AUTH */

    if (!old_style)
    {
	char *p;

	sound_name = rptp_parse(0, "sound");

	p = rptp_parse(0, "size");
	if (!p)
	{
	    connection_reply(c, "%cerror=\"missing `size=<value>'\" command=put client-data=\"%s\"",
			     RPTP_ERROR, client_data);
	    return 0;
	}
	sound_size = atoi(p);

	p = rptp_parse(0, "id");
	if (p && *p && p[0] == '#')
	{
	    spool_id = atoi(p + 1);
	}
    }
    else
	/* old-style */
    {
	sound_name = argv[1];
	sound_size = atoi(argv[2]);
	old_style++;
    }

    if (spool_id)
    {
	SPOOL *sp;
#ifdef HAVE_HELPERS
	HELPER *hp;
#endif

	sp = spool_find(spool_id);
	if (!sp)
	{
	    connection_reply(c, "%cerror=\"`%d' no such spool id\" command=put client-data=\"%s\"",
			     RPTP_ERROR, spool_id, client_data);
	    return 0;
	}
	else if (sp->sound[sp->curr_sound]->type != SOUND_FLOW)
	{
	    connection_reply(c, "%cerror=\"`%d' spool id is not a flow\" command=put client-data=\"%s\"",
			     RPTP_ERROR, spool_id, client_data);
	    return 0;
	}

	connection_reply(c, "%cid=#%d size=%d command=put client-data=\"%s\"",
			 RPTP_OK, spool_id, sound_size, client_data);

#ifdef HAVE_HELPERS
	/* XXX - it isn't known yet whether or not this sound
	   will need a helper.  Check here too. */
	hp = helper_lookup(sp->sound[sp->curr_sound]->path);
	if (hp)
	{
	    SOUND *s = sp->sound[sp->curr_sound];
	    e = event_create(EVENT_PIPE_FLOW, spool_id, s);
	    event_insert(c, e);

	    s->status = SOUND_READY;
	    sound_map(s);
	    spool_ready(s);
	}
	else
#endif /* HAVE_HELPERS */
	{
	    e = event_create(EVENT_READ_FLOW, spool_id, sound_size);
	    event_insert(c, e);
	}

	return 0;
    }
    else
    {
	/* strip pathnames -- files can only be put in the cache directory */
	p = strrchr(sound_name, '/');
	if (p)
	{
	    sound_name = p + 1;
	}
	name = cache_name(sound_name);

	s = sound_lookup(name, SOUND_DONT_FIND, NULL);
	if (s != NULL)
	{
	    connection_reply(c, "%cerror=\"%s already is in the cache\" command=put client-data=\"%s\"",
			     RPTP_ERROR, sound_name, client_data);
	    return 0;
	}

	if (cache_free(sound_size) < 0)
	{
	    connection_reply(c, "%cerror=\"the cache is full\" command=put client-data=\"%s\"",
			     RPTP_ERROR, client_data);
	}
	else
	{
	    fd = cache_create(name, sound_size);
	    if (fd < 0)
	    {
		connection_reply(c, "%cerror=\"cache error\" command=put client-data=\"%s\"",
				 RPTP_ERROR, client_data);
	    }
	    else
	    {
		report(REPORT_NOTICE, "%s put %s %d\n",
		       inet_ntoa(c->sin.sin_addr), sound_name, sound_size);
		s = sound_insert(name, SOUND_NOT_READY, SOUND_FILE);
		if (old_style)
		{
		    connection_reply(c, "%c%s %d", RPTP_OK, sound_name, sound_size);
		}
		else
		{
		    connection_reply(c, "%csound=\"%s\" size=%d command=put client-data=\"%s\"",
			      RPTP_OK, sound_name, sound_size, client_data);
		}
		e = event_create(EVENT_READ_SOUND, fd, buffer_create(), sound_size, s);
		event_insert(c, e);
	    }
	}

	return 0;
    }
}

#ifdef __STDC__
static int
command_list(CONNECTION *c, int argc, char **argv)
#else
static int
command_list(c, argc, argv)
    CONNECTION *c;
    int argc;
    char **argv;
#endif
{
    EVENT *e;
    char *client_data = default_client_data;
    BUFFER *b;

    if (strchr(command_buffer, '='))
    {
	client_data = rptp_parse(command_buffer, "client-data");
	if (!client_data)
	{
	    client_data = default_client_data;
	}
    }

    if (argv[1] == NULL || strcmp(argv[1], "sounds") == 0)
    {
	b = sound_list_create();
	if (b)
	{
	    e = event_create(EVENT_WRITE, b);
	    event_insert(c, e);
	}
	else
	{
	    connection_reply(c, "%cerror=\"no sounds available\" command=list client-data=\"%s\"",
			     RPTP_ERROR, client_data);
	}
    }
    else if (strcmp(argv[1], "connections") == 0)
    {
	b = connection_list_create();
	if (b)
	{
	    e = event_create(EVENT_WRITE, b);
	    event_insert(c, e);
	}
	else
	{
	    connection_reply(c, "%cerror=\"no connections available\" command=list client-data=\"%s\"",
			     RPTP_ERROR, client_data);
	}
    }
    else if (strcmp(argv[1], "servers") == 0)
    {
	if (server_list)
	{
	    e = event_create(EVENT_WRITE, server_list);
	    event_insert(c, e);
	}
	else
	{
	    connection_reply(c, "%cerror=\"no servers available\" command=list client-data=\"%s\"",
			     RPTP_ERROR, client_data);
	}
    }
    else if (strcmp(argv[1], "spool") == 0)
    {
	b = spool_list_create();
	if (b)
	{
	    e = event_create(EVENT_WRITE, b);
	    event_insert(c, e);
	}
	else
	{
	    connection_reply(c, "%cerror=\"no spool available\" command=list client-data=\"%s\"",
			     RPTP_ERROR, client_data);
	}
    }
#ifdef AUTH
    else if (strcmp(argv[1], "hosts") == 0)
    {
	b = host_list;
	if (b)
	{
	    e = event_create(EVENT_WRITE, b);
	    event_insert(c, e);
	}
	else
	{
	    connection_reply(c, "%cerror=\"no hosts available\" command=list client-data=\"%s\"",
			     RPTP_ERROR, client_data);
	}
    }
#endif /* AUTH */
    else
    {
	connection_reply(c, "%cerror=\"cannot list `%s'\" command=list client-data=\"%s\"",
			 RPTP_ERROR, argv[1], client_data);
    }

    return 0;
}

#ifdef __STDC__
static int
command_find(CONNECTION *c, int argc, char **argv)
#else
static int
command_find(c, argc, argv)
    CONNECTION *c;
    int argc;
    char **argv;
#endif
{
    SOUND *s;
    char *sound_name = NULL;
    int old_style = 0;
    char *client_data = default_client_data;

    if (strchr(command_buffer, '='))
    {
	sound_name = rptp_parse(command_buffer, "sound");
	client_data = rptp_parse(0, "client-data");
	if (!client_data)
	{
	    client_data = default_client_data;
	}
    }
    if (!sound_name)
    {
	sound_name = argv[1];
	old_style++;
    }

    s = sound_lookup(sound_name, SOUND_DONT_FIND, NULL);
    if (s == NULL || s->status != SOUND_READY)
    {
	report(REPORT_NOTICE, "%s find %s - not found\n",
	       inet_ntoa(c->sin.sin_addr), sound_name);
	connection_reply(c, "%cerror=\"%s not found\" command=find client-data=\"%s\"",
			 RPTP_ERROR, sound_name, client_data);
    }
    else
    {
	report(REPORT_NOTICE, "%s find %s %d\n",
	       inet_ntoa(c->sin.sin_addr), sound_name, s->size);
	if (old_style)
	{
	    connection_reply(c, "%c%s %d", RPTP_OK, s->name, s->size);
	}
	else
	{
	    connection_reply(c, "%csound=\"%s\" size=%d command=find client-data=\"%s\"",
			     RPTP_OK, s->name, s->size, client_data);
	}
    }

    return 0;
}

#ifdef __STDC__
static int
command_access(CONNECTION *c, int argc, char **argv)
#else
static int
command_access(c, argc, argv)
    CONNECTION *c;
    int argc;
    char **argv;
#endif
{
    char buf[4];
    char *client_data = default_client_data;

    if (strchr(command_buffer, '='))
    {
	client_data = rptp_parse(command_buffer, "client-data");
	if (!client_data)
	{
	    client_data = default_client_data;
	}
    }

    buf[0] = '\0';
#ifdef AUTH
    if (host_access(c->sin, HOST_READ))
    {
	strcat(buf, "r");
    }
    if (host_access(c->sin, HOST_WRITE))
    {
	strcat(buf, "w");
    }
    if (host_access(c->sin, HOST_EXECUTE))
    {
	strcat(buf, "x");
    }
#else /* AUTH */
    strcat(buf, "rwx");
#endif /* AUTH */

    connection_reply(c, "%caccess=%s command=access client-data=\"%s\"",
		     RPTP_OK, buf, client_data);

    return 0;
}

#ifdef __STDC__
static int
command_execute(CONNECTION *c, int argc, char **argv)
#else
static int
command_execute(c, argc, argv)
    CONNECTION *c;
    int argc;
    char **argv;
#endif
{
    SPOOL *sp;
    int id = do_execute(c, argc, argv);

    if (id > 0)
    {
	sp = spool_find(id);
	connection_reply(c, "%cid=#%d sound=\"%s\" command=%s client-data=\"%s\" list-name=\"%s\"",
			 RPTP_OK, id,
			 sp->curr_attrs->sound,
			 argv[0],
			 sp->curr_attrs->client_data,
			 sp->rp->list_name);
    }

    return 0;
}

/*
 * Return -1 for errors, 0 for stop, pause, continue, and return
 * a spool id for play.
 */
#ifdef __STDC__
static int
do_execute(CONNECTION *c, int argc, char **argv)
#else
static int
do_execute(c, argc, argv)
    CONNECTION *c;
    int argc;
    char **argv;
#endif
{
    int old_style = 0;
    int i, n, command = RPLAY_PLAY, val = 0;
    RPLAY *rp;
    int volume, list_count, count, priority, sample_rate;
    int do_random = 0;
    int do_search = 1;		/* search by default */
    int input = SOUND_FILE;
    int input_offset = 0;
    int input_format = 0;
    int input_byte_order = 0;
    int input_sample_rate = 0;
    float input_precision = 0;
    int input_channels = 0;
    int input_storage = SOUND_STORAGE_NONE;	/* Don't store flows by default. */
    char *client_data = default_client_data;
    char *list_name = NULL;

    if (strcmp(argv[0], "play") == 0)
    {
	command = RPLAY_PLAY;
    }
    else if (strcmp(argv[0], "stop") == 0)
    {
	command = RPLAY_STOP;
    }
    else if (strcmp(argv[0], "pause") == 0)
    {
	command = RPLAY_PAUSE;
    }
    else if (strcmp(argv[0], "continue") == 0)
    {
	command = RPLAY_CONTINUE;
    }
    else if (strcmp(argv[0], "done") == 0)
    {
	command = RPLAY_DONE;
    }

    if (strchr(command_buffer, '='))
    {
	client_data = rptp_parse(command_buffer, "client-data");
	if (!client_data)
	{
	    client_data = default_client_data;
	}
    }
    else
    {
	old_style++;
    }

#ifdef AUTH
    if (!host_access(c->sin, HOST_EXECUTE))
    {
	report(REPORT_NOTICE, "%s %s access denied\n", argv[0],
	       inet_ntoa(c->sin.sin_addr));
	connection_reply(c, "%cerror=\"access denied\" command=%s client-data=\"%s\"",
			 RPTP_ERROR, argv[0], client_data);
	return -1;
    }
#endif /* AUTH */

    rp = rplay_create(command);
    if (rp == NULL)
    {
	connection_reply(c, "%cerror=\"%s failed\" command=%s client_data=\"%s\"",
			 RPTP_ERROR, argv[0], argv[0], client_data);
	return -1;
    }

    volume = RPLAY_DEFAULT_VOLUME;
    count = RPLAY_DEFAULT_COUNT;
    list_count = RPLAY_DEFAULT_LIST_COUNT;
    priority = RPLAY_DEFAULT_PRIORITY;
    sample_rate = RPLAY_DEFAULT_SAMPLE_RATE;

    if (!old_style)
    {
	char *name, *value;

	rptp_parse(command_buffer, 0);
	while (name = rptp_parse(0, 0))
	{
	    value = rptp_parse(0, name);

	    if (!value || !*value)
	    {
		continue;
	    }
	    else if (strcmp(name, "sound") == 0
		     || (command != RPLAY_PLAY && strcmp(name, "id") == 0))
	    {
		val = rplay_set(rp, RPLAY_APPEND,
				RPLAY_SOUND, value,
				RPLAY_VOLUME, volume,
				RPLAY_COUNT, count,
				RPLAY_SAMPLE_RATE, sample_rate,
				RPLAY_RPTP_SEARCH, do_search,
				RPLAY_CLIENT_DATA, client_data,
				NULL);
		if (val < 0)
		{
		    connection_reply(c, "%cerror=\"%s failed\" command=%s client-data=\"%s\"",
				 RPTP_ERROR, argv[0], argv[0], client_data);
		    return -1;
		}
	    }
	    else if (strcmp(name, "client-data") == 0)
	    {
		client_data = value;
	    }
	    else if (strcmp(name, "volume") == 0)
	    {
		volume = atoi(value);
	    }
	    else if (strcmp(name, "count") == 0)
	    {
		count = atoi(value);
	    }
	    else if (strcmp(name, "list-count") == 0)
	    {
		list_count = atoi(value);
	    }
	    else if (strcmp(name, "priority") == 0)
	    {
		priority = atoi(value);
	    }
	    else if (strcmp(name, "sample-rate") == 0)
	    {
		sample_rate = atoi(value);
	    }
	    else if (strcmp(name, "search") == 0
		     || strcmp(name, "rptp-search") == 0)
	    {
		do_search = is_true(value);
	    }
	    else if (strcmp(name, "random") == 0)
	    {
		do_random = is_true(value);
	    }
	    else if (strcmp(name, "list-name") == 0)
	    {
		list_name = value;
	    }
	    else if (strcmp(name, "input") == 0)
	    {
		input = string_to_input(value);
	    }
	    else if (strcmp(name, "input-offset") == 0)
	    {
		input_offset = atoi(value);
	    }
	    else if (strcmp(name, "input-format") == 0)
	    {
		input_format = string_to_audio_format(value);
	    }
	    else if (strcmp(name, "input-byte-order") == 0)
	    {
		input_byte_order = string_to_byte_order(value);
	    }
	    else if (strcmp(name, "input-sample-rate") == 0)
	    {
		input_sample_rate = atoi(value);
	    }
	    else if (strcmp(name, "input-bits") == 0)
	    {
		input_precision = atof(value);
	    }
	    else if (strcmp(name, "input-channels") == 0)
	    {
		input_channels = atoi(value);
	    }
	    else if (strcmp(name, "input-storage") == 0)
	    {
		input_storage = string_to_storage(value);
	    }
	    else if (strcmp(name, "input-info") == 0)
	    {
		char *p = strtok(value, ",");
		if (p) input_format = string_to_audio_format(p);

		p = strtok(NULL, ",");
		if (p) input_sample_rate = atoi(p);

		p = strtok(NULL, ",");
		if (p) input_precision = atoi(p);

		p = strtok(NULL, ",");
		if (p) input_channels = atoi(p);

		p = strtok(NULL, ",");
		if (p) input_byte_order = string_to_byte_order(p);

		p = strtok(NULL, ",");
		if (p) input_offset = atoi(p);
	    }
	}
    }
    else
	/* old-style */
    {
	extern char *optarg;
	extern int optind;

	optind = 0;
	while ((n = getopt(argc, argv, "+P:R:N:n:v:")) != -1)
	{
	    switch (n)
	    {
	    case 'v':
		volume = atoi(optarg);
		break;

	    case 'n':
		count = atoi(optarg);
		break;

	    case 'N':
		list_count = atoi(optarg);
		break;

	    case 'P':
		priority = atoi(optarg);
		break;

	    case 'R':
		sample_rate = atoi(optarg);
		break;

	    default:
		argc = optind;
		break;
	    }
	}

	if (argc == optind)
	{
	    for (i = 0; i < NCOMMANDS; i++)
	    {
		if (strcmp(commands[i].name, argv[0]) == 0)
		{
		    connection_reply(c, "%cerror=\"usage: %s %s\" command=%s",
				     RPTP_ERROR, commands[i].name, commands[i].usage,
				     argv[0]);
		    return -1;
		}
	    }
	}

	while (argv[optind] != NULL)
	{
	    val = rplay_set(rp, RPLAY_APPEND,
			    RPLAY_SOUND, argv[optind++],
			    RPLAY_VOLUME, volume,
			    RPLAY_COUNT, count,
			    RPLAY_SAMPLE_RATE, sample_rate,
			    RPLAY_RPTP_SEARCH, do_search,
			    NULL);
	    if (val < 0)
	    {
		connection_reply(c, "%cerror=\"%s failed\" command=%s",
				 RPTP_ERROR, argv[0], argv[0]);
		return -1;
	    }
	}
    }

    if (rplay_set(rp, RPLAY_LIST_COUNT, list_count, NULL) < 0)
    {
	connection_reply(c, "%cerror=\"%s failed\" command=%s client-data=\"%s\"",
			 RPTP_ERROR, argv[0], argv[0], client_data);
	return -1;
    }

    if (rplay_set(rp, RPLAY_PRIORITY, priority, NULL) < 0)
    {
	connection_reply(c, "%cerror=\"%s failed\" command=%s client-data=\"%s\"",
			 RPTP_ERROR, argv[0], argv[0], client_data);
	return -1;
    }

    if (do_random && rplay_set(rp, RPLAY_RANDOM_SOUND, NULL) < 0)
    {
	connection_reply(c, "%cerror=\"%s failed\" command=%s client-data=\"%s\"",
			 RPTP_ERROR, argv[0], argv[0], client_data);
	return -1;
    }

    if (list_name && rplay_set(rp, RPLAY_LIST_NAME, list_name, NULL) < 0)
    {
	connection_reply(c, "%cerror=\"%s failed\" command=%s client-data=\"%s\"",
			 RPTP_ERROR, argv[0], argv[0], client_data);
	return -1;
    }

    /* Flows */
    if (input == SOUND_FLOW && command == RPLAY_PLAY)
    {
	int spool_id, i;
	SOUND *s;
	char buf[RPTP_MAX_LINE], *sound_name, *p;

#ifdef AUTH
	/* Flows require `write' access along with the previously
	   checked `execute' access. */
	if (!host_access(c->sin, HOST_WRITE))
	{
	    report(REPORT_NOTICE, "%s %s access denied\n", argv[0],
		   inet_ntoa(c->sin.sin_addr));
	    connection_reply(c, "%cerror=\"access denied\" command=%s client-data=\"%s\"",
			     RPTP_ERROR, argv[0], client_data);
	    return -1;
	}
#endif /* AUTH */

	/* Generate a unique sound name. */
	sound_name = (char *) rplay_get(rp, RPLAY_SOUND, 0);
	if (!sound_name)
	{
	    connection_reply(c, "%cerror=\"missing required `sound' attribute\" command=%s client-data=\"%s\"",
			     RPTP_ERROR, argv[0], client_data);
	    return -1;
	}
	p = strrchr(sound_name, '/');
	if (p)
	{
	    sound_name = p + 1;
	}
	i = 0;
	do
	{
	    if (i)
	    {
		SNPRINTF(SIZE(buf, sizeof(buf)), "rplay%d-%s", i, sound_name);
	    }
	    else
	    {
		SNPRINTF(SIZE(buf, sizeof(buf)), sound_name);
	    }
	    i++;
	}
	while (sound_lookup(buf, SOUND_DONT_FIND, NULL));

	sound_name = cache_name(buf);
	rplay_set(rp, RPLAY_CHANGE, 0,
		  RPLAY_SOUND, sound_name,
		  RPLAY_RPTP_SEARCH, FALSE,	/* never search */
		  NULL);
	s = sound_insert(sound_name, SOUND_NOT_READY, SOUND_FLOW);
	if (!s)
	{
	    connection_reply(c, "%cerror=\"%s failed\" command=%s client-data=\"%s\"",
			     RPTP_ERROR, argv[0], argv[0], client_data);
	    return -1;
	}

	s->offset = input_offset;
	s->format = input_format;
	s->byte_order = input_byte_order;
	s->sample_rate = input_sample_rate;
	s->input_precision = input_precision;
	s->output_precision = input_precision;
	s->channels = input_channels;
	s->storage = input_storage;

	/* Grab a spool entry. */
	spool_id = rplayd_play(rp, c->sin);
	if (spool_id < 0)
	{
	    connection_reply(c, "%cerror=\"spool full\" command=%s client-data=\"%s\"",
			     RPTP_ERROR, argv[0], client_data);
	    sound_delete(s, 0);
	    return -1;
	}

	connection_reply(c, "%cid=#%d sound=\"%s\" command=%s client-data=\"%s\"",
			 RPTP_OK, spool_id, buf, argv[0], client_data);

	return 0;
    }
    else
    {
	switch (command)
	{
	case RPLAY_PLAY:
	    val = rplayd_play(rp, c->sin);
	    break;

	case RPLAY_STOP:
	    val = rplayd_stop(rp, c->sin);
	    break;

	case RPLAY_PAUSE:
	    val = rplayd_pause(rp, c->sin);
	    break;

	case RPLAY_CONTINUE:
	    val = rplayd_continue(rp, c->sin);
	    break;

	case RPLAY_DONE:
	    val = rplayd_done(rp, c->sin);
	    break;
	}

	if (val == 0)
	{
	    connection_reply(c, "%cmessage=\"%s successful\" command=%s client-data=\"%s\"",
			     RPTP_OK, argv[0], argv[0], client_data);
	}
	else if (val < 0)
	{
	    connection_reply(c, "%cerror=\"%s failed\" command=%s client-data=\"%s\"",
			     RPTP_ERROR, argv[0], argv[0], client_data);
	}

	return val;
    }
}

/* OBSOLETE */
#ifdef __STDC__
static int
command_volume(CONNECTION *c, int argc, char **argv)
#else
static int
command_volume(c, argc, argv)
    CONNECTION *c;
    int argc;
    char **argv;
#endif
{
    int volume;
    int n;

#ifdef AUTH
    if (!host_access(c->sin, HOST_EXECUTE))
    {
	report(REPORT_NOTICE, "%s volume access denied\n",
	       inet_ntoa(c->sin.sin_addr));
	connection_reply(c, "%cerror=\"access denied\" command=volume", RPTP_ERROR);
	return 0;
    }
#endif /* AUTH */

    if (argv[1] == NULL)
    {
	volume = rplay_audio_get_volume();
	if (volume >= 0)
	{
	    connection_reply(c, "%c%d", RPTP_OK, volume);
	}
	else
	{
	    connection_reply(c, "%cerror=\"volume failed\" command=volume", RPTP_ERROR);
	}
    }
    else if (*argv[1] == '+')
    {
	volume = rplay_audio_get_volume();
	if (volume < 0)
	{
	    connection_reply(c, "%cerror=\"volume failed\" command=volume", RPTP_ERROR);
	}

	volume += atoi(argv[1] + 1);
	n = rplay_audio_set_volume(volume);
	if (n >= 0)
	{
	    connection_reply(c, "%c%d", RPTP_OK, n);
	    connection_notify(0, NOTIFY_VOLUME, n);
	}
	else
	{
	    connection_reply(c, "%cerror=\"volume failed\" command=volume", RPTP_ERROR);
	}
    }
    else if (*argv[1] == '-')
    {
	volume = rplay_audio_get_volume();
	if (volume < 0)
	{
	    connection_reply(c, "%cerror=\"volume failed\" command=volume", RPTP_ERROR);
	}

	volume -= atoi(argv[1] + 1);
	n = rplay_audio_set_volume(volume);
	if (n >= 0)
	{
	    connection_reply(c, "%c%d", RPTP_OK, n);
	    connection_notify(0, NOTIFY_VOLUME, n);
	}
	else
	{
	    connection_reply(c, "%cerror=\"volume failed\" command=volume", RPTP_ERROR);
	}
    }
    else
    {
	volume = atoi(argv[1]);
	n = rplay_audio_set_volume(volume);
	if (n >= 0)
	{
	    connection_reply(c, "%c%d", RPTP_OK, n);
	    connection_notify(0, NOTIFY_VOLUME, n);
	}
	else
	{
	    connection_reply(c, "%cerror=\"volume failed\" command=volume", RPTP_ERROR);
	}
    }

    return 0;
}

#ifdef __STDC__
static int
command_info(CONNECTION *c, int argc, char **argv)
#else
static int
command_info(c, argc, argv)
    CONNECTION *c;
    int argc;
    char **argv;
#endif
{
    SOUND *s;
    char *sound_name = NULL;
    char *client_data = default_client_data;

#ifdef AUTH
    if (!host_access(c->sin, HOST_READ))
    {
	report(REPORT_NOTICE, "%s info %s - read access denied\n",
	       inet_ntoa(c->sin.sin_addr), argv[1]);
	connection_reply(c, "%cerror=\"access denied\" command=info client-data=\"%s\"",
			 RPTP_ERROR, client_data);
	return 0;
    }
#endif /* AUTH */

    if (strchr(command_buffer, '='))
    {
	sound_name = rptp_parse(command_buffer, "sound");
	client_data = rptp_parse(0, "client-data");
	if (!client_data)
	{
	    client_data = default_client_data;
	}
    }
    if (!sound_name)
    {
	sound_name = argv[1];
    }

    /* s = sound_lookup (sound_name, SOUND_CREATE, NULL); */
    s = sound_lookup(sound_name, SOUND_LOAD, NULL);
    if (s == NULL || s->status != SOUND_READY)
    {
	report(REPORT_NOTICE, "%s info %s - not found\n",
	       inet_ntoa(c->sin.sin_addr), sound_name);
	connection_reply(c, "%cerror=\"%s not found\" command=info client-data=\"%s\"",
			 RPTP_ERROR, sound_name, client_data);
    }
    else
    {
	connection_reply(c, "\
%csound=\"%s\" size=%d bits=%g sample-rate=%d channels=%d format=%s byte-order=%s input=%s seconds=%.2f command=info client-data=\"%s\"",
			 RPTP_OK,
			 s->name,
			 s->size,
			 s->input_precision,
			 s->sample_rate,
			 s->channels,
			 audio_format_to_string(s->format),
			 byte_order_to_string(s->byte_order),
			 input_to_string(s->type),
			 s->sample_rate && s->samples ? (double) s->samples / s->sample_rate : 0,
			 client_data);
    }

    return 0;
}

#ifdef __STDC__
static int
command_version(CONNECTION *c, int argc, char **argv)
#else
static int
command_version(c, argc, argv)
    CONNECTION *c;
    int argc;
    char **argv;
#endif
{
    char *client_data;

    client_data = rptp_parse(command_buffer, "client-data");
    if (!client_data)
    {
	client_data = default_client_data;
    }

    connection_reply(c, "%cversion=%s command=version client-data=\"%s\"",
		     RPTP_OK, RPLAY_VERSION, client_data);

    return 0;
}

#ifdef __STDC__
static void
do_events(char *event_string, int *mask, int *spool_id)
#else
static void
do_events(event_string, mask, spool_id)
    char *event_string;
    int *mask;
    int *spool_id;
#endif
{
    char buf[RPTP_MAX_LINE];
    int first = 1;
    char *p;
    int operator, bit;

    *spool_id = 0;

    strncpy(buf, event_string, sizeof(buf));

    while (p = strtok(first ? buf : NULL, ",| "))
    {
	first = 0;
	bit = 0;
	operator = 0;

	if (*p == '+')
	{
	    operator = 0;
	    p++;
	}
	else if (*p == '-')
	{
	    operator = 1;
	    p++;
	}

	if (strcmp(p, "all") == 0 || strcmp(p, "any") == 0)
	{
	    bit = NOTIFY_ANY;
	}
	else if (strcmp(p, "none") == 0)
	{
	    *mask = 0;
	}
	else if (strcmp(p, "play") == 0)
	{
	    bit = NOTIFY_PLAY;
	}
	else if (strcmp(p, "stop") == 0)
	{
	    bit = NOTIFY_STOP;
	}
	else if (strcmp(p, "pause") == 0)
	{
	    bit = NOTIFY_PAUSE;
	}
	else if (strcmp(p, "continue") == 0)
	{
	    bit = NOTIFY_CONTINUE;
	}
	else if (strcmp(p, "volume") == 0)
	{
	    bit = NOTIFY_VOLUME;
	}
	else if (strcmp(p, "done") == 0)
	{
	    bit = NOTIFY_DONE;
	}
	else if (strcmp(p, "skip") == 0)
	{
	    bit = NOTIFY_SKIP;
	}
	else if (strcmp(p, "state") == 0)
	{
	    bit = NOTIFY_STATE;
	}
	else if (strcmp(p, "flow") == 0)
	{
	    bit = NOTIFY_FLOW;
	}
	else if (p[0] == '#')
	{
	    *spool_id = atoi(p + 1);
	}
	else if (strcmp(p, "modify") == 0)
	{
	    bit = NOTIFY_MODIFY;
	}
	else if (strcmp(p, "level") == 0)
	{
	    bit = NOTIFY_LEVEL;
	}
	else if (strcmp(p, "position") == 0)
	{
	    bit = NOTIFY_POSITION;
	}

	if (operator == 0)
	{
	    SET_BIT(*mask, bit);
	}
	else
	{
	    CLR_BIT(*mask, bit);
	}
    }

    /* If a spool id is set and there's no mask, assign a default
       mask which will notify everything than can happen to the
       spool entry. */
    if (*spool_id && !*mask)
    {
	SET_BIT(*mask, NOTIFY_SPOOL);
    }
}

#ifdef __STDC__
static int
command_wait(CONNECTION *c, int argc, char **argv)
#else
static int
command_wait(c, argc, argv)
    CONNECTION *c;
    int argc;
    char **argv;
#endif
{
    EVENT *e;
    char *client_data;

    client_data = rptp_parse(command_buffer, "client-data");
    if (!client_data)
    {
	client_data = default_client_data;
    }

    /* `wait' - wait for anything */
    if (argc == 1)
    {
	e = event_create(EVENT_NOTIFY);
	SET_BIT(e->wait_mask, NOTIFY_ANY);
	event_insert(c, e);
	return 0;
    }
    /* `wait id=#spool-id' or `wait event=event,event,event' */
    else if (argc == 2 && strchr(argv[1], '='))
    {
	char *value;

	value = rptp_parse(argv[1], "id");
	if (value && *value)
	{
	    e = event_create(EVENT_NOTIFY);
	    SET_BIT(e->wait_mask, NOTIFY_DONE);
	    e->id = atoi(value + 1);
	    event_insert(c, e);
	    return 0;
	}

	value = rptp_parse(argv[1], "event");
	if (value && *value)
	{
	    e = event_create(EVENT_NOTIFY);
	    do_events(value, &e->wait_mask, &e->id);
	    event_insert(c, e);
	    return 0;
	}

	connection_reply(c, "%cerror=\"unknown wait attribute `%s'\" command=wait client-data=\"%s\"",
			 RPTP_ERROR, argv[1], client_data);
	return 0;
    }
    /* `wait play ...' */
    else if (strcmp(argv[1], "play") == 0)
    {
	int id;

	id = do_execute(c, argc - 1, argv + 1);
	if (id > 0)
	{
	    e = event_create(EVENT_NOTIFY);
	    SET_BIT(e->wait_mask, NOTIFY_DONE);
	    e->id = id;
	    event_insert(c, e);
	}

	return 0;
    }
    else
    {
	/* XXX: Need to fix command_buffer */
	int i, n;
	command_buffer[0] = '\0';
	n = 0;
	for (i = 1; i < argc; i++)
	{
	    SNPRINTF(SIZE(command_buffer + n, sizeof(command_buffer) - n), argv[i]);
	    n += strlen(argv[i]);
	    if (i + 1 != argc)
	    {
		SNPRINTF(SIZE(command_buffer + n, sizeof(command_buffer) - n), " ");
		n++;
	    }
	}

	return do_command(c, argc - 1, argv + 1);
    }
}

#ifdef __STDC__
static int
command_status(CONNECTION *c, int argc, char **argv)
#else
static int
command_status(c, argc, argv)
    CONNECTION *c;
    int argc;
    char **argv;
#endif
{
    EVENT *e;

    e = event_create(EVENT_WRITE, rplayd_status());
    event_insert(c, e);

    return 0;
}

/* OBSOLETE */
#ifdef __STDC__
static int
command_application(CONNECTION *c, int argc, char **argv)
#else
static int
command_application(c, argc, argv)
    CONNECTION *c;
    int argc;
    char **argv;
#endif
{
    int i, n;
    char buf[RPTP_MAX_LINE];

    buf[0] = '\0';
    n = 0;
    for (i = 1; i < argc; i++)
    {
	SNPRINTF(SIZE(buf + n, sizeof(buf) - n), argv[i]);
	n += strlen(argv[i]);
	if (i + 1 != argc)
	{
	    SNPRINTF(SIZE(buf + n, sizeof(buf) - n), " ");
	    n++;
	}
    }

    if (c->application)
    {
	free(c->application);
    }

    c->application = strdup(buf);

    connection_reply(c, "%c%s", RPTP_OK, c->application);

    return 0;
}

#ifdef DEBUG
#ifdef __STDC__
static int
command_die(CONNECTION *c, int argc, char **argv)
#else
static int
command_die(c, argc, argv)
    CONNECTION *c;
    int argc;
    char **argv;
#endif
{
    report(REPORT_DEBUG, "time to die...\n");
    connection_close(c);
    done(0);
    return -1;			/* not reached */
}
#endif /* DEBUG */

#ifdef __STDC__
static int
command_reset(CONNECTION *c, int argc, char **argv)
#else
static int
command_reset(c, argc, argv)
    CONNECTION *c;
    int argc;
    char **argv;
#endif
{
    char *client_data;

    client_data = rptp_parse(command_buffer, "client-data");
    if (!client_data)
    {
	client_data = default_client_data;
    }

#ifdef AUTH
    if (!host_access(c->sin, HOST_EXECUTE))
    {
	report(REPORT_NOTICE, "%s reset access denied\n", inet_ntoa(c->sin.sin_addr));
	connection_reply(c, "%cerror=\"access denied\" command=reset client-data=\"%s\"",
			 RPTP_ERROR, client_data);
	return 0;
    }
#endif /* AUTH */

    need_reset();

    connection_reply(c, "%cmessage=\"reset successful\" command=reset client-data=\"%s\"",
		     RPTP_OK, client_data);

    return 0;
}

#ifdef __STDC__
static int
command_skip(CONNECTION *c, int argc, char **argv)
#else
static int
command_skip(c, argc, argv)
    CONNECTION *c;
    int argc;
    char **argv;
#endif
{
    SPOOL *sp;
    int id = 0, count = 1;	/* default */
    int nskipped = 0;
    char *p;
    char *client_data;

    client_data = rptp_parse(command_buffer, "client-data");
    if (!client_data)
    {
	client_data = default_client_data;
    }

#ifdef AUTH
    if (!host_access(c->sin, HOST_EXECUTE))
    {
	report(REPORT_NOTICE, "%s skip access denied\n", inet_ntoa(c->sin.sin_addr));
	connection_reply(c, "%cerror=\"access denied\" command=skip client-data=\"%s\"",
			 RPTP_ERROR, client_data);
	return 0;
    }
#endif /* AUTH */

    p = rptp_parse(0, "id");
    if (p)
    {
	id = atoi(p + 1);
    }
    p = rptp_parse(0, "count");
    if (p)
    {
	count = atoi(p);
    }

    for (sp = spool; sp; sp = sp->next)
    {
	if (!id || sp->id == id)
	{
	    spool_skip(sp, count);
	    nskipped++;
	}
    }

    if (nskipped)
    {
	connection_reply(c, "%cmessage=\"skipped\" command=skip client-data=\"%s\"",
			 RPTP_OK, client_data);
    }
    else
    {
	connection_reply(c, "%cerror=\"skip failed\" command=skip client-data=\"%s\"",
			 RPTP_ERROR, client_data);
    }

    return 0;
}

#ifdef __STDC__
static int
command_set(CONNECTION *c, int argc, char **argv)
#else
static int
command_set(c, argc, argv)
    CONNECTION *c;
    int argc;
    char **argv;
#endif
{
    char *name, *value;
    BUFFER *b;
    EVENT *e;
    int volume_changed = 0;
    int priority_threshold_changed = 0;
    int audio_port_changed = 0;
    int notify_enabled = 0;
    SPOOL *sp;
    int notify_mask = 0;
    int notify_id = 0;
    char *client_data;
    char buf[RPTP_MAX_LINE];

    client_data = rptp_parse(command_buffer, "client-data");
    if (!client_data)
    {
	client_data = default_client_data;
    }

#ifdef AUTH
    if (!host_access(c->sin, HOST_EXECUTE))
    {
	report(REPORT_NOTICE, "%s set access denied\n", inet_ntoa(c->sin.sin_addr));
	connection_reply(c, "%cerror=\"access denied\" command=set client-data=\"%s\"",
			 RPTP_ERROR, client_data);
	return 0;
    }
#endif /* AUTH */

    b = buffer_create();
    b->buf[0] = RPTP_OK;
    b->buf[1] = '\0';
    b->nbytes = 1;

    rptp_parse(command_buffer, 0);
    while (name = rptp_parse(0, 0))
    {
	value = rptp_parse(0, name);

	if (strcmp(name, "application") == 0)	/* `application=value' */
	{
	    if (value && *value)
	    {
		if (c->application)
		{
		    free(c->application);
		}
		c->application = strdup(value);
	    }
	    SNPRINTF(SIZE(buf, sizeof(buf)), "application=\"%s\"",
		     c->application ? c->application : "-1");
	    strncat(b->buf + b->nbytes, buf, BUFFER_SIZE - b->nbytes);
	    b->nbytes += strlen(buf);
	}
	else if (strcmp(name, "notify") == 0)	/* `notify=[event,...]' */
	{
	    if (value)
	    {
		do_events(value, &c->notify_mask, &c->notify_id);
	    }

	    if (!c->notify_mask)
	    {
		SNPRINTF(SIZE(buf, sizeof(buf)), "notify=none");
		strncat(b->buf + b->nbytes, buf, BUFFER_SIZE - b->nbytes);
		b->nbytes += strlen(buf);
	    }
	    else
	    {
		SNPRINTF(SIZE(buf, sizeof(buf)), "notify=\"%s\"", value);
		strncat(b->buf + b->nbytes, buf, BUFFER_SIZE - b->nbytes);
		b->nbytes += strlen(buf);
		notify_enabled++;
	    }
	}
	else if (strcmp(name, "volume") == 0)	/* `volume=[+|-]value' */
	{
	    int volume = rplay_audio_get_volume();

	    if (value && *value)
	    {
		switch (value[0])
		{
		case '+':
		    volume += atoi(value + 1);
		    break;

		case '-':
		    volume -= atoi(value + 1);
		    break;

		case '*':
		    volume *= atoi(value + 1);
		    break;

		case '/':
		    volume /= atoi(value + 1);
		    break;

		default:
		    volume = atoi(value);
		}

		volume = rplay_audio_set_volume(volume);
	    }

	    if (volume >= 0)
	    {
		SNPRINTF(SIZE(buf, sizeof(buf)), "volume=%d", volume);
		strncat(b->buf + b->nbytes, buf, BUFFER_SIZE - b->nbytes);
		b->nbytes += strlen(buf);
		volume_changed++;
	    }
	    else
	    {
		SNPRINTF(SIZE(buf, sizeof(buf)), "volume=-1");
		strncat(b->buf + b->nbytes, buf, BUFFER_SIZE - b->nbytes);
		b->nbytes += strlen(buf);
	    }
	}
	else if (strcmp(name, "priority-threshold") == 0)	/* `priority-threshold=value' */
	{
	    if (value && *value)
	    {
		rplay_priority_threshold = atoi(value);
		if (rplay_priority_threshold < RPLAY_MIN_PRIORITY)
		{
		    rplay_priority_threshold = RPLAY_MIN_PRIORITY;
		}
		else if (rplay_priority_threshold > RPLAY_MAX_PRIORITY)
		{
		    rplay_priority_threshold = RPLAY_MAX_PRIORITY;
		}

		priority_threshold_changed++;
	    }
	    SNPRINTF(SIZE(buf, sizeof(buf)), "priority-threshold=%d",
		     rplay_priority_threshold);
	    strncat(b->buf + b->nbytes, buf, BUFFER_SIZE - b->nbytes);
	    b->nbytes += strlen(buf);
	}
	else if (strcmp(name, "notify-rate") == 0)	/* `notify-rate=value' */
	{
	    if (value && *value)
	    {
		int i;
		double rate;

		rate = atof(value);

		for (i = 0; i < NOTIFY_RATE_MAX; i++)
		{
		    c->notify_rate[i].rate = rate;
		    c->notify_rate[i].next = 0.0;
		}
	    }

	    SNPRINTF(SIZE(buf, sizeof(buf)), "level-notify-rate=%.2f position-notify-rate=%.2f",
		     c->notify_rate[NOTIFY_RATE_LEVEL].rate,
		     c->notify_rate[NOTIFY_RATE_POSITION].rate);
	    strncat(b->buf + b->nbytes, buf, BUFFER_SIZE - b->nbytes);
	    b->nbytes += strlen(buf);
	}
	else if (strcmp(name, "level-notify-rate") == 0)
	{
	    if (value && *value)
	    {
		c->notify_rate[NOTIFY_RATE_LEVEL].rate = atof(value);
		c->notify_rate[NOTIFY_RATE_LEVEL].next = 0.0;
	    }

	    SNPRINTF(SIZE(buf, sizeof(buf)), "level-notify-rate=%.2f",
		     c->notify_rate[NOTIFY_RATE_LEVEL].rate);
	    strncat(b->buf + b->nbytes, buf, BUFFER_SIZE - b->nbytes);
	    b->nbytes += strlen(buf);
	}
	else if (strcmp(name, "position-notify-rate") == 0)
	{
	    if (value && *value)
	    {
		c->notify_rate[NOTIFY_RATE_POSITION].rate = atof(value);
		c->notify_rate[NOTIFY_RATE_POSITION].next = 0.0;
	    }

	    SNPRINTF(SIZE(buf, sizeof(buf)), "position-notify-rate=%.2f",
		     c->notify_rate[NOTIFY_RATE_POSITION].rate);
	    strncat(b->buf + b->nbytes, buf, BUFFER_SIZE - b->nbytes);
	    b->nbytes += strlen(buf);
	}
	else if (strcmp(name, "audio-port") == 0)
	{
	    int n;

	    optional_port = rplay_audio_port;

	    if (strstr(value, "none"))
	    {
		optional_port = 0;
	    }
	    if (strstr(value, "speaker"))
	    {
		SET_BIT(optional_port, RPLAY_AUDIO_PORT_SPEAKER);
	    }
	    if (strstr(value, "headphone"))
	    {
		SET_BIT(optional_port, RPLAY_AUDIO_PORT_HEADPHONE);
	    }
	    if (strstr(value, "lineout"))
	    {
		SET_BIT(optional_port, RPLAY_AUDIO_PORT_LINEOUT);
	    }

	    if (optional_port != rplay_audio_port)
	    {
		n = rplay_audio_init();
		if (n >= 0)
		{
		    audio_port_changed++;
		}
	    }
	    else
	    {
		n = 0;
	    }

	    if (n < 0)
	    {
		SNPRINTF(SIZE(buf, sizeof(buf)), "audio-port=-1");
		strncat(b->buf + b->nbytes, buf, BUFFER_SIZE - b->nbytes);
		b->nbytes += strlen(buf);
	    }
	    else
	    {
		SNPRINTF(SIZE(buf, sizeof(buf)), "audio-port=%s", value);
		strncat(b->buf + b->nbytes, buf, BUFFER_SIZE - b->nbytes);
		b->nbytes += strlen(buf);
	    }
	}
	else if (strcmp(name, "audio-info") == 0)
	{
	    char info[1024], *p;
	    SPOOL *sp, *sp_next;
	    int bufsize, rate;

	    /* Stop all sounds. */
	    for (sp = spool; sp; sp = sp_next)
	    {
		sp_next = sp->next;
		spool_stop(sp);
	    }

	    /* Example: ulaw,8000,8,1 */
	    strncpy(info, value, sizeof(info));
	    p = strtok(info, ", ");
	    if (p)
		optional_format = string_to_audio_format(p);
	    p = strtok(NULL, ",");
	    if (p)
		optional_sample_rate = atoi(p);
	    p = strtok(NULL, ",");
	    if (p)
		optional_precision = atoi(p);
	    p = strtok(NULL, ",");
	    if (p)
		optional_channels = atoi(p);

#ifndef HAVE_OSS
	    bufsize = rplay_audio_bufsize;	/* XXX: force recalculation */
	    rplay_audio_bufsize = 0;
#endif /* !HAVE_OSS */

	    rate = rplay_audio_rate;
	    rplay_audio_rate = 0;

	    if (rplayd_audio_init() < 0)
	    {
		SNPRINTF(SIZE(buf, sizeof(buf)), "audio-info=-1");
		strncat(b->buf + b->nbytes, buf, BUFFER_SIZE - b->nbytes);
		b->nbytes += strlen(buf);
#ifndef HAVE_OSS
		rplay_audio_bufsize = bufsize;
#endif /* !HAVE_OSS */
		rplay_audio_rate = rate;
	    }
	    else
	    {
		SNPRINTF(SIZE(buf, sizeof(buf)), "audio-info=%s", value);
		strncat(b->buf + b->nbytes, buf, BUFFER_SIZE - b->nbytes);
		b->nbytes += strlen(buf);
	    }
	}
	else if (strcmp(name, "audio-close") == 0)
	{
	    if (value && *value)
	    {
		extern int rplay_audio_timeout;
		rplay_audio_timeout = atoi(value);
		SNPRINTF(SIZE(buf, sizeof(buf)), "audio-close=%d", rplay_audio_timeout);
		strncat(b->buf + b->nbytes, buf, BUFFER_SIZE - b->nbytes);
		b->nbytes += strlen(buf);
	    }
	}
	else
	{
	    SNPRINTF(SIZE(buf, sizeof(buf)), "%s=-1", name);
	    strncat(b->buf + b->nbytes, buf, BUFFER_SIZE - b->nbytes);
	    b->nbytes += strlen(buf);
	}

	SNPRINTF(SIZE(buf, sizeof(buf)), " ");
	strncat(b->buf + b->nbytes, buf, BUFFER_SIZE - b->nbytes);
	b->nbytes += strlen(buf);
    }

    SNPRINTF(SIZE(buf, sizeof(buf)), "command=set client-data=\"%s\"\r\n",
	     client_data);
    strncat(b->buf + b->nbytes, buf, BUFFER_SIZE - b->nbytes);
    b->nbytes += strlen(buf);

    e = event_create(EVENT_WRITE, b);
    event_insert(c, e);

    if (notify_enabled)
    {
	/* Notify the current status.  */
	for (sp = spool; sp; sp = sp->next)
	{
	    switch (sp->state)
	    {
	    case SPOOL_PLAY:
		connection_notify(c, NOTIFY_PLAY, sp);
		break;

	    case SPOOL_PAUSE:
		connection_notify(c, NOTIFY_PAUSE, sp);
		break;
	    }
	}
	connection_notify(c, NOTIFY_VOLUME, rplay_audio_volume);

	e = event_create(EVENT_NOTIFY);
	event_insert(c, e);
    }

    if (volume_changed)
    {
	connection_notify(0, NOTIFY_VOLUME, rplay_audio_volume);
    }

    if (priority_threshold_changed || audio_port_changed)
    {
	connection_notify(0, NOTIFY_STATE);
    }

    connection_want_level_notify = 0;
    for (c = connections; c; c = c->next)
    {
	if (BIT(c->notify_mask, NOTIFY_LEVEL)
	    || (c->event && BIT(c->event->wait_mask, NOTIFY_LEVEL)))
	{
	    connection_want_level_notify++;
	}
    }

    return 0;
}

#ifdef __STDC__
static int
command_modify(CONNECTION *c, int argc, char **argv)
#else
static int
command_modify(c, argc, argv)
    CONNECTION *c;
    int argc;
    char **argv;
#endif
{
    SPOOL *sp;
    int id = 0;
    char *p;
    int new_sample_rate = -1;
    int new_volume = -1;
    int new_count = -1;
    int new_priority = -1;
    int new_list_count = -1;
    char *new_client_data = NULL;
    int modified, total_modified = 0;

#ifdef AUTH
    if (!host_access(c->sin, HOST_EXECUTE))
    {
	report(REPORT_NOTICE, "%s modify access denied\n", inet_ntoa(c->sin.sin_addr));
	connection_reply(c, "%cerror=\"access denied\" command=modify", RPTP_ERROR);
	return 0;
    }
#endif /* AUTH */

    p = rptp_parse(command_buffer, "id");
    if (!p || !*p)
    {
	connection_reply(c, "%cerror=\"missing `id'\" command=modify", RPTP_ERROR);
	return 0;
    }
    id = atoi(p + 1);

    p = rptp_parse(0, "count");
    if (p)
    {
	new_count = atoi(p);
    }

    p = rptp_parse(0, "list-count");
    if (p)
    {
	new_list_count = atoi(p);
    }

    p = rptp_parse(0, "priority");
    if (p)
    {
	new_priority = atoi(p);
    }

    p = rptp_parse(0, "sample-rate");
    if (p)
    {
	new_sample_rate = atoi(p);
    }

    p = rptp_parse(0, "volume");
    if (p)
    {
	new_volume = atoi(p);
    }

    p = rptp_parse(0, "client-data");
    if (p)
    {
	new_client_data = p;
    }

    for (sp = spool; sp; sp = sp->next)
    {
	if (!id || sp->id == id)
	{
	    modified = 0;
	    if (new_count != -1)
	    {
		spool_set_count(sp, new_count);
		modified++;
	    }
	    if (new_list_count != -1)
	    {
		spool_set_list_count(sp, new_list_count);
		modified++;
	    }
	    if (new_priority != -1)
	    {
		spool_set_priority(sp, new_priority);
		modified++;
	    }
	    if (new_sample_rate != -1)
	    {
		spool_set_sample_rate(sp, new_sample_rate);
		modified++;
	    }
	    if (new_volume != -1)
	    {
		spool_set_volume(sp, new_volume);
		modified++;
	    }
	    if (new_client_data)
	    {
		spool_set_client_data(sp, new_client_data);
		modified++;
	    }

	    if (modified)
	    {
		connection_notify(c, NOTIFY_MODIFY, sp);
		total_modified++;
	    }
	}
    }

    if (total_modified)
    {
	connection_reply(c, "%cmessage=\"modified\" command=modify", RPTP_OK);
    }
    else
    {
	connection_reply(c, "%cerror=\"nothing modified\" command=modify", RPTP_ERROR);
    }

    return 0;
}

#ifdef __STDC__
static int
command_monitor(CONNECTION *c, int argc, char **argv)
#else
static int
command_monitor(c, argc, argv)
    CONNECTION *c;
    int argc;
    char **argv;
#endif
{
    EVENT *e;
    
#ifdef AUTH
    if (!host_access(c->sin, HOST_MONITOR))
    {
	report(REPORT_NOTICE, "%s monitor - monitor access denied\n",
	       inet_ntoa(c->sin.sin_addr));
	connection_reply(c, "%cerror=\"access denied\" command=monitor",
			 RPTP_ERROR);
	return 0;
    }
#endif /* AUTH */

    if (monitor_count == 0)
    {
	monitor_alloc();
    }
    monitor_count++;

    connection_reply(c, "%caudio-info=\"%s,%d,%d,%d,%s\"", RPTP_OK,
		     audio_format_to_string(rplay_audio_format),
		     rplay_audio_sample_rate,
		     rplay_audio_precision,
		     rplay_audio_channels,
		     byte_order_to_string(RPLAY_AUDIO_BYTE_ORDER));
    e = event_create(EVENT_WAIT_MONITOR);
    event_insert(c, e);

    c->monitor = 1;

    return 0;
}
