Fix up merge conflicts by hand.
Conflicts:
configure.ac
error.h
server.ggo
Features
========
-configurable audio streaming software
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+configurable audio streaming software:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
para_server streams binary audio data (mp3/ogg files) over
local or remote networks. It contains two built-in streamers:
the http streamer and the ortp streamer.
configurable audio file selectors:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- There are three audio file selectors (aka "database tools")
- available:
+ There are three audio file selectors available:
- random
- - plm (playlist manager)
+ - playlist
- mysql
The first two of these are rather simple, and they are always
small memory footprint:
~~~~~~~~~~~~~~~~~~~~~~~
paraslash is lightweight. The stripped binary of para_server
- with all its features compiled in (mysql/random dbtool,
- mp3/ogg support, http/ortp support) is about 110K on i386
- under Linux. para_audiod is even smaller.
+ with all its features compiled in (mysql/random/playlist
+ selector, mp3/ogg support, http/ortp support) is about 110K
+ on i386 under Linux. para_audiod is even smaller.
command line interface, including shell:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
to retrieve the list of available commands and some server info.
-Choose your database tool (dbtool)
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-You have two options:
+Choose an audio file selector
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+paraslash has three different audio file selectors: random (default),
+playlist and mysql.
- 1. Use the mysql dbtool which comes with paraslash and requires
- mysql.
+ The random selector chooses files randomly from the given
+ directory.
- 2. If you can not use the mysql dbtool and you just want
- to quickly make paraslash working, use the random dbtool.
- The directory which is searched for audio files can be given
- via the server option --random_dbtool_dir.
+ The playlist selector allows to send a playlist to para_server
+ via the lpl (load playlist) command. para_server will choose
+ files from the loaded playlist in sequential order.
- Note, however, that this database tool is really dopey. It
- scans the given directory on every audio file change and
- chooses one randomly. There is no further functionality.
+ The mysql selector stores information about your audio
+ files in a mysql database. It is much more involved than
+ the other two selectors and lets you chose files in many
+ interesting ways. If you like to use the mysql selector,
+ read README.mysql and follow the instructions given there.
+ Return to this document when ready.
-The current database tool can be changed at runtime via
+The current audio file selector can be changed at runtime via
- para_client cdt new_dbtool
+ para_client cdt new_selector
-If you have choosen 1. above, read README.mysql and follow the
-instructions given there. Return to this document when ready.
Start streaming manually
system = $(shell uname -rs)
cc_version = $(shell $(CC) --version | head -n 1)
version = @PACKAGE_VERSION@
-codename = atomic duality
+codename = oriented abstraction
DEBUG_CPPFLAGS += -Wno-sign-compare -g -Wunused -Wundef -W
misc := bash_completion
headers := para.h server.h SFont.h crypt.h list.h http.h send.h ortp.h rc4.h \
close_on_fork.h afs.h db.h gcc-compat.h recv.h filter.h audiod.h \
- grab_client.h error.h net.h ringbuffer.h daemon.h string.h
+ grab_client.h error.h net.h ringbuffer.h daemon.h string.h ipc.h
scripts := install-sh configure
autocrap := Makefile.in config.h.in configure.ac autogen.sh
tarball := web/sync/doc pics fonts $(c_sources) $(sample_conf) $(headers) \
maintainer-clean: distclean
rm -f $(gengetopts_c) $(gengetopts_h) *.tar.bz2 \
$(grutatxt_html) ChangeLog* config.h configure \
- config.h.in
+ config.h.in skencil/*.pdf skencil/*.ps
+ rm -rf doc
install: all
umask 022 && \
NEWS
====
-0.?.? (to be announced) "atomic duality"
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- o update to gengetopt-2.16
+0.?.? (to be announced) "oriented abstraction"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+0.2.11 (2006-03-11) "atomic duality"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Here it is, the first paraslash release developed with git. There
+are fairly many user-visible changes in this release. As two out of
+the three "database tools" of paraslash don't use a database at all,
+they are now called "audio file selectors" instead.
+
+
+ o the cdt command (change database tool) becomes chs (change
+ selector)
+
+ o no more colon separators: The syntax of some options of
+ para_audiod and para_filter have changed. Use --help for
+ more info (and some examples).
+
+ o update to gengetopt-2.16 (thanks to Lorenzo Bettini)
- o switch from cvs to git
+ o switch from cvs to git (should've done that earlier)
o the new ipc subsystem
- o new database tool: plm, the playlist manager
+ o new audio file selector: playlist
- o para_server: the dopey file selector is now called "random",
- and it is selected by default. Use the --dbtool option to
- choose another dbtool at startup, or the cdt command to switch
- between the supported dbtools.
+ o para_server: the dopey selector is now called "random",
+ and is the default selector. Use the --selector option to
+ choose another selector at startup, or the chs command to
+ change the selector at runtime.
o X86_64 fixes (thanks to Steffen Klassert)
All senders have the same set of commands that allow to
control the access permissions of the stream.
- para_server needs a "database tool" to work, mainly to
- determine which song to stream next. There are three such
- tools available: random, plm and mysql. The former chooses
- audio files randomly and plm, the playlist manager, can handle
+ para_server needs an "audio file selector" to work, mainly
+ to determine which song to stream next. There are three
+ selectors available: random, playlist and mysql. The former
+ chooses audio files randomly and playlist can handle, well,
playlists. Both are always supported.
- The (optional) mysql database tool connects to a mysql server
- which holds information on your audio files. It has several
- unusual features, see README.mysql for details.
+ The optional mysql selector connects to a mysql server which
+ holds information on your audio files. It has several unusual
+ features, see README.mysql for details.
- para_client (obligatory):
- para_gui (optional, but recommended):
- Themable ncurses-based gui. It calls para_audioc and presents
- the obtained information in an ncurses window. para_gui
- provides key-bindings for the most common commands and new
- key-bindings can be added easily.
+ Themable ncurses-based gui. It calls para_audioc and presents
+ the obtained information in an ncurses window. para_gui
+ provides key-bindings for the most common commands and new
+ key-bindings can be added easily.
- para_sdl_gui (optional):
- SDL-based gui. Similar to para_gui but presents its output in
- an X window (fullscreen mode is also available) and can display
- jpg images on a per song basis. para_sdl_gui provides an input
- prompt to enter arbitrary commands. However, it can also be used
- non-interactively (e.g. as a screen saver) via the -i switch.
+ SDL-based gui. Similar to para_gui but presents its output
+ in an X window (fullscreen mode is also available) and can
+ display jpg images on a per song basis. para_sdl_gui provides
+ an input prompt to enter arbitrary commands. However, it
+ can also be used non-interactively (e.g. as a screen saver)
+ via the -i switch.
-- para_krell (optional, only useful in conjunction with the mysql dbtool):
+- para_krell (optional, only useful in conjunction with the mysql selector):
A plugin for gkrellm which shows small pictures of the
current song. It allows you to launch 27 different commands
A (Linux-only) alarm clock and volume-fader.
-- para_dbadm (optional, only useful in conjunction with the mysql dbtool):
+- para_dbadm (optional, only useful in conjunction with the mysql selector):
Very simple curses-based frontend which uses libmenu. Useful
for quickly changing the attributes of the current song
(e.g. from para_gui as an external command).
-- para_slider (optional, only useful in conjunction with the mysql dbtool):
+- para_slider (optional, only useful in conjunction with the mysql selector):
A small X application which shows a scrollbar for each
attribute defined in the mysql database. It creates a stream
- software mixing, e.g. ALSA and the direct mixing plugin (dmix)
-If you want to use the mysql-based dbtool (recommended), you also need
+If you want to use the mysql-based audio file selector, you also need
- mysql-server
- mysql-client
LIMITATIONS:
~~~~~~~~~~~~
-The mysql database tool assumes that the basenames of your audio files
-are unique. If this is not the case, don't use this database tool,
-rename your files, or create your own database tool.
+The mysql selector assumes that the basenames of your audio files are
+unique. If this is not the case, don't use this selector, rename your
+files, or create your own one.
THE AUTHOR:
~~~~~~~~~~~
README.mysql
============
-This file describes how to use the mysql database tool which comes
-with the paraslash package.
+This file describes how to use the mysql audio file selector which
+comes with the paraslash package.
It assumes you have already installed mysql and paraslash as described
in INSTALL, so read README and INSTALL before proceeding.
Or, restart the server.
-Switch to the mysql dbtool
-~~~~~~~~~~~~~~~~~~~~~~~~~~
+Switch to the mysql audio file selector
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Actually, the mysql database tool should already be selected (just
-ignore the warning about the missing database). To verify that it is
-indeed activated, type
+The command
- para_client cdt
+ para_client chs
-which prints the name of the current database tool. If the mysql
-dbtool is not selected, try
+prints the name of the current selector. Try
- para_client cdt mysql
+ para_client chs mysql
-If this doesn't work either, it means that some required config options
-were not specified (check the log for more info) or that para_server
-was built without mysql support. Type
+to switch to the mysql selector. If this doesn't work, it means that
+some required config options were not specified (check the log for
+more info) or that para_server was built without mysql support. Type
para_client si
-to find out. If mysql is not mentioned as a supported database tool,
+to find out. If mysql is not mentioned as a supported selector,
you'll have to recompile.
Create a new database
~~~~~~~~~~~~~~~~~~~~~
-Once the mysql database tool is selected, create the database:
+Once the mysql selector is activated, create the database:
para_client cdb
- para_client cdt mysql
+ para_client chs mysql
-The second command forces para_server to re-init the mysql dbtool.
+The second command forces para_server to re-init the mysql selector.
Check the log. There should not be any warnings or errors.
para_client ls
-prints the list of all files known by the mysql dbtool. If the list
+prints the list of all files known by the mysql selector. If the list
is empty, double check the mysql_audio_file_dir option.
Attribute usage
~~~~~~~~~~~~~~~
-An attribute is simply a bit which can be set for each sound file
+An attribute is simply a bit which can be set for each audio file
individually. You may have as many attributes as you like. A new
attribute "test" is created by
para_client laa
lists all available attributes. You can set the "test" attribute for
-the current song by executing
+the current audio file by executing
para_client sa test+
-or for any particular song by
+or for any particular audio file by
para_client sa test+ filename
-You can unset the attribute "test" for the current song with
+Unset the attribute "test" for the current audio file with
- para_client sa test-
+ para_client sa test-
-and you can drop the test attribute entirely from the database with
+and drop the test attribute entirely from the database with
para_client da test
Stream usage
~~~~~~~~~~~~
-A stream is a pair of expressions in terms of attributes and other
-meta data contained in the database. The first, boolian, expression
-determines the set of songs which are permitted in this stream. The
-second, integer, expression determines the order in which permitted
-songs are going to be fed to the audio file sender(s).
+A stream is a pair of expressions in terms of attributes and other data
+contained in the database. The first, boolian, expression determines
+the set of audio files which are admissible in this stream. The second,
+integer, expression determines the order in which admissible files
+are going to be fed to the audio file sender(s).
To create a new stream called "my_stream", put arbitrary many (including
none) accept or deny lines and one or zero score lines into some
para_client stradd my_stream < tmpfile
-adds the stream "my_stream" to dbtool's stream database.
+adds the stream "my_stream" to the table of streams.
If the stream definition is really short, you may also just pipe it to
the client rather than using temporary files. Like this:
Example:
- Assume you already have an attribute "test" and you'd like to
- to restrict the set of songs being played to those having the
- "test" attribute set. Define a new stream "only_test" by
+ Assume you already have an attribute "test" and you'd like to
+ to restrict the set of songs being played to those having the
+ "test" attribute set. Define a new stream "only_test" by
echo 'accept: IS_SET(test)' | para_client stradd only_test
only the desired songs are going to be played.
There is no need to keep the temporary files containing the stream
-definition since you can always query the database to get it back:
+definition since you can always use the strq command to get it back:
para_client strq only_test
LASTPLAYED()/1440
-This means that each song's score is just the number of days that went
-by since this song has been played (one day is 1440 minutes). This
-is fine in many cases since the dbtool then always chooses that
-admissible song, which wasn't played for the longest time.
+This means that the score of an audio file is just the number of days
+that went by since it has been played the last time (one day is 1440
+minutes). In other words, the mysql selector choses that admissible
+file which wasn't played for the longest time.
-However, one disadvantage of this scoring sheme is that new songs,
+However, one disadvantage of this scoring sheme is that new files,
once played, are going to be deferred for a possibly very long period
-depending on the size of your collection of (admissible) songs. Hence
+depending on the size of your collection of (admissible) files. Hence
the following scoring rule comes into mind:
- score: -NUMPLAYED()
+ score: -NUMPLAYED()
-since this gives newer songs, i.e. songs to which you haven't listen to
+since this gives newer files, i.e. files to which you haven't listen to
that often, a higher score than older songs you already know by heart.
You can also use a combination of these two methods:
- score: LASTPLAYED()/1440 - 10 * NUMPLAYED()
+ score: LASTPLAYED()/1440 - 10 * NUMPLAYED()
which subtracts 10 score points for each time paraslash has played
-this song.
+this file.
-Another useful feature for scoring is due to the fact that "true"
-expands to one and "false" to zero. So you can also use the
-IS_SET/IS_N_SET/NAME_LIKE macros in a score line to give
-your favorite band "bar" some extra points:
+Another useful feature for scoring is due to the fact that
+"true" expands to one and "false" to zero. So you can also use the
+IS_SET/IS_N_SET/NAME_LIKE macros in a score line to give your favorite
+band "bar" some extra points:
score: 40 * IS_SET(foo) + 20 * NAME_LIKE(%bar%) + LASTPLAYED()/1440
Pictures
~~~~~~~~
-dbtool can also magage images that, when associated with certain songs,
-can be displayed by para_sdl_gui and para_krell when one of these songs
-is playing. It is also possible to just retrieve the current image via
+The mysql selector can also magage images that, when associated
+with one or more audio files, can be displayed by para_sdl_gui and
+para_krell. It is also possible to just retrieve the current image via
para_client pic > filename
/** \file afs.c audio file sending functions
*
- * This contains the audio sending part of para_server which is independent
- * of the current audio format, database tool and of the activated senders.
+ * This contains the audio sending part of para_server which is independent of
+ * the current audio format, audio file selector and of the activated senders.
*/
#include <sys/time.h> /* gettimeofday */
static struct timeval eof_barrier;
extern struct misc_meta_data *mmd;
-extern struct dbtool dblist[];
+extern struct audio_file_selector dblist[];
extern struct sender senders[];
extern struct gengetopt_args_info conf;
static void get_song(void)
{
- char **sl = dblist[mmd->dbt_num].get_audio_file_list(10);
+ char **sl = dblist[mmd->selector_num].get_audio_file_list(10);
int i;
if (!sl)
continue;
}
mmd->num_played++;
- if (dblist[mmd->dbt_num].update_audio_file)
- dblist[mmd->dbt_num].update_audio_file(sl[i]);
+ if (dblist[mmd->selector_num].update_audio_file)
+ dblist[mmd->selector_num].update_audio_file(sl[i]);
PARA_DEBUG_LOG("%s", "success\n");
mmd->new_afs_status_flags &= (~AFS_NEXT);
gettimeofday(&now, NULL);
free(tmp);
tmp = make_message("%s:\n%s:\n%s:\n", status_item_list[SI_DBINFO1],
status_item_list[SI_DBINFO2], status_item_list[SI_DBINFO3]);
- strcpy(mmd->dbinfo, tmp);
+ strcpy(mmd->selector_info, tmp);
free(tmp);
mmd->filename[0] = '\0';
mmd->size = 0;
stat_pipe = -1;
kill_all_decoders();
for (i = 0; i < RINGBUFFER_SIZE; i++)
- ringbuffer_add(stat_item_ringbuf, para_strdup(NULL));
+ free(ringbuffer_add(stat_item_ringbuf, para_strdup(NULL)));
dump_empty_status();
length_seconds = 0;
offset_seconds = 0;
playing = 0;
msg = make_message("%s:no connection to para_server\n",
status_item_list[SI_STATUS_BAR]);
- ringbuffer_add(stat_item_ringbuf, msg);
+ free(ringbuffer_add(stat_item_ringbuf, msg));
stat_client_write(msg);
- free(msg);
}
static void __noreturn clean_exit(int status, const char *msg)
if (!line)
return;
- ringbuffer_add(stat_item_ringbuf, line);
+ free(ringbuffer_add(stat_item_ringbuf, para_strdup(line)));
stat_client_write(line);
itemnum = stat_line_valid(line);
if (itemnum < 0)
/** \file command.c does client authentication and executes server commands */
-
#include <malloc.h> /* mallinfo */
#include <sys/time.h> /* gettimeofday */
#include "crypt.h"
extern const char *status_item_list[NUM_STAT_ITEMS];
extern struct misc_meta_data *mmd;
extern struct gengetopt_args_info conf;
-extern struct dbtool dblist[];
+extern struct audio_file_selector dblist[];
extern struct audio_format afl[];
extern struct sender senders[];
extern char *user_list;
static int com_pause(int, int, char **);
static int com_next(int, int, char **);
static int com_nomore(int, int, char **);
-static int com_cdt(int, int, char **);
+static int com_chs(int, int, char **);
static int com_ff(int, int, char **);
static int com_jmp(int, int, char **);
static int com_sender(int, int, char **);
/* commands that are handled by the server itself */
static struct server_command cmd_struct[] = {
{
-.name = "cdt",
-.handler = com_cdt,
+.name = "chs",
+.handler = com_chs,
.perms = DB_READ | DB_WRITE,
-.description = "change database tool",
-.synopsis = "cdt [name_of_new_dbtool]",
+.description = "change the current audio file selector",
+.synopsis = "chs [new_selector]",
.help =
-"Deactivate current dbtool and activate name_of_new_dbtool. If no\n"
-"argument was given, print the current database tool.\n"
+"Shutdown the current selector and activate new_selector. If no\n"
+"argument was given, print the name of the current selector.\n"
},
{
return para_strdup("paused");
}
-
/*
* return human readable permission string. Never returns NULL.
*/
status_item_list[SI_STATUS_BAR], bar ? bar : "(none)",
status_item_list[SI_STATUS], status,
status_item_list[SI_STATUS_FLAGS], flags,
- status_item_list[SI_DBTOOL], dblist[nmmd->dbt_num].name,
+ status_item_list[SI_SELECTOR], dblist[nmmd->selector_num].name,
status_item_list[SI_OFFSET], offset,
status_item_list[SI_FORMAT], audio_format_name(nmmd->audio_format),
- nmmd->dbinfo,
+ nmmd->selector_info,
nmmd->audio_file_info,
status_item_list[SI_UPTIME], ut,
{
int i, ret;
char *ut;
- char *dbtools = NULL, *sender_info = NULL, *sender_list = NULL;
+ char *selectors = NULL, *sender_info = NULL, *sender_list = NULL;
struct mallinfo mi = mallinfo();
if (argc)
return -E_COMMAND_SYNTAX;
mmd_lock();
for (i = 0; dblist[i].name; i++) {
- dbtools = para_strcat(dbtools, dblist[i].name);
- dbtools = para_strcat(dbtools, " ");
+ selectors = para_strcat(selectors, dblist[i].name);
+ selectors = para_strcat(selectors, " ");
}
for (i = 0; senders[i].name; i++) {
char *info = senders[i].info();
"mallinfo: %d\n"
"connections (active/accepted/total): %u/%u/%u\n"
"current loglevel: %i\n"
- "supported database tools: %s\n"
+ "supported audio file selectors: %s\n"
"supported audio formats: %s\n"
"supported senders: %s\n"
"%s",
mmd->num_commands,
mmd->num_connects,
conf.loglevel_arg,
- dbtools,
+ selectors,
SUPPORTED_AUDIO_FORMATS,
sender_list,
sender_info
);
mmd_unlock();
free(ut);
- free(dbtools);
+ free(selectors);
free(sender_list);
free(sender_info);
return ret;
/* stat */
static int com_stat(int socket_fd, int argc, char **argv)
{
-// char *old_stat = NULL, *old_dbinfo = NULL;
int ret, num = 0;/* status will be printed that many
* times. num <= 0 means: print forever
*/
*handler = para_strdup("para_server"); /* server commands */
return cmd;
}
- /* not found, look for dbtool commands */
+ /* not found, look for commands supported by the current selector */
mmd_lock();
if (handler)
- *handler = make_message("the %s database tool", dblist[mmd->dbt_num].name);
- cmd = dblist[mmd->dbt_num].cmd_list;
+ *handler = make_message("the %s selector",
+ dblist[mmd->selector_num].name);
+ cmd = dblist[mmd->selector_num].cmd_list;
mmd_unlock();
for (; cmd->name; cmd++)
if (!strcmp(cmd->name, name))
if ((ret = send_description(fd, cmd_struct, "server", 0)) < 0)
return ret;
mmd_lock();
- handler = para_strdup(dblist[mmd->dbt_num].name);
- cmd = dblist[mmd->dbt_num].cmd_list;
+ handler = para_strdup(dblist[mmd->selector_num].name);
+ cmd = dblist[mmd->selector_num].cmd_list;
mmd_unlock();
ret = send_description(fd, cmd, handler, 0);
free(handler);
return 1;
}
-static int com_cdt(int fd, int argc, char **argv)
+static int com_chs(int fd, int argc, char **argv)
{
int i, ret;
if (!argc) {
- char *dbtool;
+ char *selector;
mmd_lock();
- dbtool = para_strdup(dblist[mmd->dbt_num].name);
+ selector = para_strdup(dblist[mmd->selector_num].name);
mmd_unlock();
- ret = send_va_buffer(fd, "%s\n", dbtool);
- free(dbtool);
+ ret = send_va_buffer(fd, "%s\n", selector);
+ free(selector);
return ret;
}
for (i = 0; dblist[i].name; i++) {
if (strcmp(dblist[i].name, argv[1]))
continue;
mmd_lock();
- mmd->dbt_change = i;
+ mmd->selector_change = i;
mmd->events++;
mmd_unlock();
return 1;
}
- return -E_BAD_DBTOOL;
+ return -E_BAD_SELECTOR;
}
/* next */
sscanf(cmdstr, "%200s%n", buf, &n);
if (!n)
- return NULL;
+ return NULL;
buf[n] = '\0';
return get_cmd_ptr(buf, NULL);
}
return (long int) ((max + 0.0) * (random() / (RAND_MAX + 1.0)));
}
-
/* Open user_list file, returns pointer to opened file on success,
* NULL on errors
*/
PARA_DEBUG_LOG("rc4 keys initialized (%u:%u)\n",
(unsigned char) rc4_buf[0],
(unsigned char) rc4_buf[RC4_KEY_LEN]);
- RC4_set_key(&rc4_recv_key, RC4_KEY_LEN, rc4_buf);
- RC4_set_key(&rc4_send_key, RC4_KEY_LEN, rc4_buf + RC4_KEY_LEN);
+ RC4_set_key(&rc4_recv_key, RC4_KEY_LEN, rc4_buf);
+ RC4_set_key(&rc4_send_key, RC4_KEY_LEN, rc4_buf + RC4_KEY_LEN);
}
static void rc4_recv(unsigned long len, const unsigned char *indata, unsigned char *outdata)
{
- RC4(&rc4_recv_key, len, indata, outdata);
+ RC4(&rc4_recv_key, len, indata, outdata);
}
+
static void rc4_send(unsigned long len, const unsigned char *indata, unsigned char *outdata)
{
- RC4(&rc4_send_key, len, indata, outdata);
+ RC4(&rc4_send_key, len, indata, outdata);
}
-
-
-
int handle_connect(int fd, struct sockaddr_in *addr)
{
int numbytes, ret, argc, use_rc4 = 0;
audiod_ldflags=""
server_cmdline_objs="server.cmdline"
-server_errlist_objs="server mp3 afs command net string signal random_dbtool time daemon stat
- crypt http_send dccp dccp_send db close_on_fork plm_dbtool ipc"
+server_errlist_objs="server mp3 afs command net string signal random_selector
+ time daemon stat crypt http_send db close_on_fork playlist_selector ipc dccp dccp_send"
server_ldflags=""
########################################################################### ssl
])
if test "$have_mysql" = "yes"; then
server_ldflags="$server_ldflags -lmysqlclient"
- server_errlist_objs="$server_errlist_objs mysql"
+ server_errlist_objs="$server_errlist_objs mysql_selector"
AC_DEFINE(HAVE_MYSQL, 1, [define to 1 to turn on mysql support])
else
- AC_MSG_WARN([no libmysqlclient, cannot build mysql-based dbtool])
+ AC_MSG_WARN([cannot build mysql-based audio file selector])
fi
########################################################################### ogg
have_ogg="yes"
*/
-/** \file db.c functions common to all database tools. */
+/** \file db.c functions common to all audio file selectors */
#include "server.cmdline.h"
#include "server.h"
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*/
-/** \file db.h data structures common to all database tools */
+/** \file db.h data structures common to all audio file selectors */
#include <sys/select.h>
-enum supported_dbtools {DBT_DOPEY,
-#ifdef HAVE_MYSQL
- DBT_MYSQL,
-#endif
- NUM_DBTOOLS
-};
int find_audio_files(const char *dirname, int (*f)(const char *, const char *));
/**
- * describes one of para_server's supported database tools
+ * describes one supported audio file selector
*
- * There is exactly one such struct for each supported database tool. During
- * the startup part of para_server the \a init() function of the activated
- * database tool gets called which fills in the other members.
+ * There is one such struct for each supported selector. During the startup
+ * part of para_server the \a init() function of the activated selector gets
+ * called which fills in all other function pointers.
*
*
*/
-struct dbtool {
+struct audio_file_selector {
/**
- * name name of this database tool
+ * name name of this selector
*/
const char *name;
/**
- * the database init routine
+ * the init routine of the selector
*
* It should check its command line options and do all necessary initialization
* like connecting to a database server.
*
* A negative return value indicates an initialization error and means that
- * this database tool should be ignored for now (it may later be activated
- * again via the cdt command).
+ * this selector should be ignored for now (it may later be activated again via
+ * the chs command).
*
* If \a init() returns success (non-negative return value), it must have
- * initialized in all non-optional function pointers of the given dbtool
+ * initialized in all non-optional function pointers of the given selector
* struct. Moreover, \a cmd_list must point to a NULL-terminated array which
- * holds the list of all commands that are supported by this database tool.
+ * holds the list of all commands that are supported by this selector.
*/
-int (*init)(struct dbtool *self);
+int (*init)(struct audio_file_selector *self);
/**
- * list of commands supported by this dbtool
+ * list of commands supported by this selector
*/
struct server_command *cmd_list;
/**
void (*update_audio_file)(char *audio_file);
/**
*
- * shutdown this database tool and free all resources
+ * shutdown this selector and free all resources
*
- * This gets called whenever the database tool changes (via the cdt command),
- * or when para_server receives the HUP signal, or when para_server shuts down.
- * It is assumed to succeed.
+ * This gets called whenever the audio file selector changes. The reason for
+ * this change might be that some user sent the chs command, that para_server
+ * receives the HUP signal, or that para_server shuts down. It is assumed to
+ * succeed.
*/
void (*shutdown)(void);
/**
*
* add file descriptors to fd_sets
*
- * The pre_select function of the activated database tool gets called just
- * before para_server enters its main select loop. The dbtool may add its own
- * file descriptors to the \a rfds or the \a wfds set.
+ * The pre_select function of the activated selector gets called just before
+ * para_server enters its main select loop. The selector may add its own file
+ * descriptors to the \a rfds or the \a wfds set.
*
* If a file descriptor was added, \a max_fileno must be increased by
* this function, if neccessary.
*/
void (*post_select)(fd_set *rfds, fd_set *wfds);
/**
- * each dbtool has its private data pointer */
+ * each selector has its private data pointer */
void *private_data;
};
-int mysql_dbtool_init(struct dbtool*);
-int plm_dbtool_init(struct dbtool*);
-int random_dbtool_init(struct dbtool*);
+int mysql_selector_init(struct audio_file_selector*);
+int playlist_selector_init(struct audio_file_selector*);
+int random_selector_init(struct audio_file_selector*);
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*/
-/** \file dbadm.c simple attribute setting utility for the mysql dbtool */
+/** \file dbadm.c simple attribute setting utility for the mysql selector */
#include "para.h"
#include <menu.h>
/** \file error.h list of error messages for all subsystems */
/** \cond list of all subsystems that support the shiny new error facility */
-enum para_subsystem {SS_RECV,
- SS_NET, SS_ORTP_RECV, SS_AUDIOD, SS_EXEC, SS_CLOSE_ON_FORK, SS_SIGNAL,
- SS_STRING, SS_DAEMON, SS_STAT, SS_TIME, SS_GRAB_CLIENT, SS_HTTP_RECV,
- SS_RECV_COMMON, SS_FILTER_CHAIN, SS_WAV, SS_COMPRESS, SS_OGGDEC, SS_FILTER,
- SS_COMMAND, SS_RANDOM_DBTOOL, SS_PLM_DBTOOL, SS_CRYPT, SS_HTTP_SEND, SS_ORTP_SEND, SS_DB, SS_OGG,
- SS_MP3, SS_MP3DEC, SS_SERVER, SS_AFS, SS_MYSQL, SS_IPC, SS_DCCP, SS_DCCP_RECV,
- SS_DCCP_SEND, SS_RINGBUFFER};
+enum para_subsystem {
+ SS_RECV,
+ SS_NET,
+ SS_ORTP_RECV,
+ SS_AUDIOD,
+ SS_EXEC,
+ SS_CLOSE_ON_FORK,
+ SS_SIGNAL,
+ SS_STRING,
+ SS_DAEMON,
+ SS_STAT,
+ SS_TIME,
+ SS_GRAB_CLIENT,
+ SS_HTTP_RECV,
+ SS_RECV_COMMON,
+ SS_FILTER_CHAIN,
+ SS_WAV,
+ SS_COMPRESS,
+ SS_OGGDEC,
+ SS_FILTER,
+ SS_COMMAND,
+ SS_RANDOM_SELECTOR,
+ SS_PLAYLIST_SELECTOR,
+ SS_CRYPT,
+ SS_HTTP_SEND,
+ SS_ORTP_SEND,
+ SS_DB,
+ SS_OGG,
+ SS_MP3,
+ SS_MP3DEC,
+ SS_SERVER,
+ SS_AFS,
+ SS_MYSQL_SELECTOR,
+ SS_IPC,
+ SS_DCCP,
+ SS_DCCP_RECV,
+ SS_DCCP_SEND,
+ SS_RINGBUFFER};
+
#define NUM_SS (SS_RINGBUFFER + 1)
extern const char **para_errlist[];
/** \endcond */
PARA_ERROR(WRITE_OK, "can not check whether fd is writable"), \
-#define RANDOM_DBTOOL_ERRORS \
+#define RANDOM_SELECTOR_ERRORS \
PARA_ERROR(FILE_COUNT, "audio file count exceeded"), \
PARA_ERROR(NOTHING_FOUND, "no audio files found"), \
-#define MYSQL_ERRORS \
+#define MYSQL_SELECTOR_ERRORS \
PARA_ERROR(MYSQL_SYNTAX, "mysql syntax error"), \
PARA_ERROR(NOTCONN, "not connected to mysql server"), \
PARA_ERROR(TOOBIG, "mysql: file too large"), \
#define COMMAND_ERRORS \
PARA_ERROR(COMMAND_SYNTAX, "syntax error in command"), \
PARA_ERROR(AUTH, "did not receive auth request"), \
- PARA_ERROR(BAD_DBTOOL, "no such database tool"), \
+ PARA_ERROR(BAD_SELECTOR, "no such audio file selector"), \
PARA_ERROR(NO_AUDIO_FILE, "no audio file"), \
PARA_ERROR(BAD_CMD, "invalid command"), \
PARA_ERROR(PERM, "permission denied"), \
PARA_ERROR(LOCK, "lock error"), \
PARA_ERROR(SENDER_CMD, "command not supported by this sender"), \
-#define PLM_DBTOOL_ERRORS \
+#define PLAYLIST_SELECTOR_ERRORS \
PARA_ERROR(LOAD_PLAYLIST, "failed to load playlist"), \
SS_ENUM(SERVER);
SS_ENUM(AFS);
SS_ENUM(COMMAND);
-SS_ENUM(RANDOM_DBTOOL);
-SS_ENUM(PLM_DBTOOL);
+SS_ENUM(RANDOM_SELECTOR);
+SS_ENUM(PLAYLIST_SELECTOR);
SS_ENUM(CRYPT);
SS_ENUM(HTTP_SEND);
SS_ENUM(ORTP_SEND);
SS_ENUM(DB);
-SS_ENUM(MYSQL);
+SS_ENUM(MYSQL_SELECTOR);
SS_ENUM(IPC);
SS_ENUM(DCCP);
SS_ENUM(DCCP_RECV);
static int parse_config(int argc, char *argv[])
{
static char *cf; /* config file */
- struct stat statbuf;
+ struct stat statbuf;
int i;
if (cmdline_parser(argc, argv, &conf))
cf = make_message("%s/.paraslash/filter.conf", home);
free(home);
}
- if (!stat(cf, &statbuf)) {
+ if (!stat(cf, &statbuf)) {
if (cmdline_parser_configfile(cf, &conf, 0, 0, 0))
return -E_FILTER_SYNTAX;
}
return 1;
printf("available filters: ");
for (i = 0; filters[i].name; i++)
- printf("%s%s", i? " " : "", filters[i].name);
- printf("\nTry para_filter -f<filtername>:-h for help on <filtername>\n");
+ printf("%s%s%s", i? " " : "", filters[i].name,
+ filters[i].parse_config? "*": "");
+ printf("\nFilters marked with \"*\" have further command line options. Try\n"
+ "\tpara_filter -f '<filtername> -h'\nfor more information.\n");
exit(EXIT_SUCCESS);
}
order matters.
Filter options may be specified for each '-f'
-option separately. Insinde these options ':'
-must be used as the separator instead of white
-space. Example:
+option separately. Note that you will have to
+quote these options like this:
- -f compress:--anticlip:--volume:2
+ -f 'compress --anticlip --volume 2'
"
string typestr="filter_spec" no multiple
/**
* check the filter command line options
*
- * \param fa the command line options (values separated by colons)
+ * \param fa the command line options
* \param conf points to the filter configuration upon successful return
*
* Check if \a fa starts with a the name of a supported filter, followed by
d[SI_STATUS_FLAGS].y = 17;
d[SI_STATUS_FLAGS].len = 10;
- d[SI_DBTOOL].prefix = "dbtool: ";
- d[SI_DBTOOL].postfix = "";
- d[SI_DBTOOL].fg = COLOR_RED;
- d[SI_DBTOOL].bg = COLOR_BLACK;
- d[SI_DBTOOL].align = CENTER;
- d[SI_DBTOOL].x = 21;
- d[SI_DBTOOL].y = 17;
- d[SI_DBTOOL].len = 20;
+ d[SI_SELECTOR].prefix = "selector: ";
+ d[SI_SELECTOR].postfix = "";
+ d[SI_SELECTOR].fg = COLOR_RED;
+ d[SI_SELECTOR].bg = COLOR_BLACK;
+ d[SI_SELECTOR].align = CENTER;
+ d[SI_SELECTOR].x = 21;
+ d[SI_SELECTOR].y = 17;
+ d[SI_SELECTOR].len = 20;
d[SI_FORMAT].prefix = "format: ";
d[SI_FORMAT].postfix = "";
rn->buf = para_calloc(BUFSIZE);
rn->private_data = para_calloc(sizeof(struct private_http_recv_data));
phd = rn->private_data;
- optind = 0;
ret = -E_HOST_INFO;
if (!(he = get_host_info(conf->host_arg)))
goto err_out;
+++ /dev/null
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
- "http://www.w3.org/TR/html4/loose.dtd">
-<html>
-<head>
- <meta http-equiv='Content-Type' content='text/html; charset=iso-8859-1'>
- <title>Paraslash</title>
- <style type="text/css">
- body {
- background-color: #000000;
- color: #cccccc;
- }
- :link { color: #990000 }
- :visited { color: #990000 }
- td.c2 {font-family: arial, helvetica, sans-serif; font-size: 80%}
- td.c1 {font-family: lucida, helvetica; font-size: 248%}
- a:hover {background:#ff0;}
- a:hover img {background:#fff;}
- </style>
- <link rel="shortcut icon" href="paraslash.ico">
-</head>
-<body>
- <basefont face="lucida, helvetica, arial" size="3">
- <table border="0" cellpadding="0" cellspacing="0">
- <tr>
- <td>
- <a href="/"><IMG SRC="paraslash.png" alt="paraslash" border="0"></a><BR>
- </td>
- <td>
- <h3>Paraslash: Play, archive, rate and stream
- large audio sets happily</h3>
-
- A set of tools for doing just what its name
- suggests.
- </td>
- </tr>
- <tr>
- <td valign="TOP">
- <br><a href="#feature_list">Feature list</a>
- <br><a href="#screenshots">Screenshots</a>
- <br><a href="#download">Download</a>
- <br><a href="#live_demo">Live Demo</a>
- <br><a href="#changes">Changes</a> <br><a
- href="#documentation">Documentation</a>
- <br><a href="#license">License</a> <br><a
- href="#author">Author</a>
- </td>
- <td Valign="TOP">
- <hr>
- <h3>Events</h3>
- <ul>
- <li>2005-12-27: paraslash-0.2.7 "transparent invariance"</li>
- <li>2005-10-29: paraslash-0.2.6 "recursive compensation"</li>
- <li>2005-10-27: <a href="doc/html/index.html">manual pages</a> online</li>
- <li>2005-10-13: paraslash-0.2.5 "aggressive resolution"</li>
- <li>2005-09-21: paraslash-0.2.4 "toxic anticipation"</li>
- <li>2005-09-01: paraslash-0.2.3 "hydrophilic movement"</li>
- <li>2005-08-19: paraslash-0.2.2 "tangential excitation"</li>
- <li>2005-08-15: paraslash-0.2.1 "surreal experience"</li>
- <li>2005-08-06: overview.pdf</li>
- <li>2005-08-06: paraslash-0.2.0 "distributed diffusion"</li>
- <li>2005-08-01: paraslash live stream</li>
- <li>2005-04-18: paraslash-0.1.7 "melting penetration"</li>
- <li>2005-03-05: paraslash-0.1.6 "asymptotic balance"</li>
- <li>2004-12-31: paraslash-0.1.5 "opaque eternity"</li>
- <li>2004-12-19: paraslash-0.1.4 "tunneling transition"</li>
- <li>2004-12-10: paraslash-0.1.3 "vanishing inertia"</li>
- <li>2004-11-28: paraslash-0.1.2 "spherical fluctuation"</li>
- <li>2004-11-05: paraslash-0.1.1 "floating atmosphere"</li>
- <li>2004-10-22: paraslash-0.1.0 "rotating cortex"</li>
- </ul>
- <hr>
- <h3><a name="feature_list">Feature list</a></h3>
- <ul>
- <li>network audio streaming software</li>
- <li>client/server tcp-networking</li>
- <li>command line interface</li>
- <li> <a href="http://www.openssl.org/">openssl</a> user authentication</li>
- <li>several grafical user interfaces</li>
- <li> <a href="http://www.mysql.com">mysql</a>-based audio file selector</li>
- </ul>
- See <a href="FEATURES.html">FEATURES</a> for a more detailed list.
- <hr>
-
- <h3><a name="screenshots">Screenshots</a></h3>
- Everybody loves screenshots, so
- <a href="screenshots/">here</a>
- we go.
- <hr>
-
- <h3><a name="download">Download</a></h3>
-
- <p> Only <a href="versions/">source</a> is available,
- including the <a href="versions/paraslash-cvs.tar.bz2">
- nightly cvs snapshot</a>. All regular releases are
- <a href="PUBLIC_KEY">cryptographically signed</a>.
- Anonymous (read-only) cvs access is also
- available. Checkout a copy with </p>
-
- <p> cvs -d
- :pserver:anonymous@cvs.systemlinux.org:/var/lib/cvs
- login </p>
- <p>(empty passwd)</p>
- <p> cvs -d :pserver:anonymous@cvs.systemlinux.org:/var/lib/cvs co paraslash </p>
-
-
- <p> Finally, you can <a href="HTML/index.html">RTFS online</a>.
- <hr>
-
- <h3><a name="live_demo">Live Demo</a></h3>
-
- <p> There is a public paraslash stream at www.paraslash.org,
- streaming
-
- <a href="http://www.digitalvibes.de/">the music of Henri Petterson</a>.
-
-
- You can listen to the stream with any mp3 player that supports
- http streaming. Both </p>
-
- <p>mpg123 http://www.paraslash.org:8009/</p>
-
- and
-
- <p>xmms http://www.paraslash.org:8009/</p>
-
- <p> are known to work.</p>
-
- <p> Moreover, there is an anonymous paraslash account
- available which you can use to have a look at paraslash
- without configuring and running para_server on your own box.
- Just download and run
-
- <a href="demo-script">this shell script</a>
-
- on your Unix system. If you prefer to do things manually,
- simply cut-and-paste the instructions given below verbatim
- to your shell. No root-privileges are required.</p>
-
-<ul>
- <li>
- Check that both aplay and mpg123 are installed on your system
- </li>
-
- <li>
- <a href="versions">Download </a>
- a recent paraslash package. You
- you need paraslash-0.2.0 or later
- for the demo, paraslash-0.1.x will not work.
- </li>
-
- <li>
-
- Install the neccessary paraslash binaries
- (you can safely ignore any warnings about
- missing software):
-
- <ul>
- <li>tar xjf paraslash-cvs.tar.bz2</li>
- <li>cd paraslash-cvs</li>
- <li>bin="para_client para_audioc para_audiod para_gui" # all we need
- <li>(./configure --prefix="$HOME" && make $bin) > /dev/null</li>
- <li> mkdir -p $HOME/bin; cp $bin $HOME/bin
- <li> export PATH=$HOME/bin:$PATH
-
- </ul>
- There should be no errors.
- </li>
- <li>
- Get the key for the anonymous account on
- www.paraslash.org:
-
- <ul>
- <li> dir="$HOME/.paraslash"; server=www.paraslash.org</li>
- <li> mkdir -p $dir</li>
- <li> wget --directory-prefix=$dir http://$server/key.anonymous</li>
- </ul>
- </li>
- <li>
- Tell para_client that we want to connect to
- www.paraslash.org as user anonymous:
-
- <ul>
- <li> conf="$dir/client.conf"; socket="$dir/socket"</li>
- <li> echo user \"anonymous\" > $conf</li>
- <li> echo hostname \"$server\" >> $conf</li>
- <li> echo key_file \"$dir/key.anonymous\" >> $conf</li>
- <li> echo socket \"$socket\" >> $dir/audioc.conf</li>
- </ul>
- </li>
- <li>
- Start para_audiod
- <ul> <li>
-
- para_audiod -d --stream_read_cmd
- "mp3:mpg123 -s http://$server:8009/"
- --stream_write_cmd "mp3:aplay -fcd" -L $dir/audiod.log
- --socket=$socket
-
- </li> </ul>
- </li><li>
- Start para_gui
- <ul> <li>
- para_gui
- </li> </ul>
- </li>
- </ul> <hr>
-
-
- <h3><a name="changes">Changes</a></h3>
- Read the complete
- <a href="ChangeLog.html">ChangeLog</a>
- or the file
- <a href="NEWS.html">NEWS</a>
- containing a brief summary of the changes for each version.
- <hr>
-
-
- <h3><a name="documentation">Documentation</a></h3>
- Have a look at this
- <a href="overview.pdf">overview</a>,
- a pdf file containing a sketch which illustrates how the pieces of paraslash work
- together. Read
- <a href="README.html">README</a>
- for general information,
- <a href="INSTALL.html">INSTALL</a>
- for installation notes, and
- <a href="README.mysql.html">README.mysql</a>
- for instructions on how to use the mysql database tool
- shipped with paraslash. There is also an online version
- of paraslash's
- <a href="doc/html/index.html">manual pages</a>.
- <hr>
-
- <h3><a name="license">License</a></h3>
- Distribution of Paraslash is covered by the GNU GPL. See file
- <a href="COPYING.html">COPYING</a>.
- <hr>
-
- <h3><a name="author">Author</a></h3>
- André Noll,
- <a href="mailto:maan@systemlinux.org"
- >maan@systemlinux.org</a>
- <p>
- Several people helped by reporting bugs, improving documentation,
- constructive discussions, or, last but not least, by writing
- free software on which this project is based on. See
- <a href="CREDITS.html">CREDITS</a>
- for an incomplete list of people.
- <hr>
-
-
- Last modified:
- <!--#flastmod virtual="" -->
- <p>
- <a href="http://validator.w3.org/check?uri=referer"><img border="0"
- src="http://www.w3.org/Icons/valid-html401"
- alt="Valid HTML 4.01!" height="31" width="88"></a>
- </p>
-
- </td>
- </table>
-</body>
-</html>
+++ /dev/null
-/*
- * Copyright (C) 1999-2006 Andre Noll <maan@systemlinux.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, USA.
- */
-
-/** \file mysql.c para_server's mysql-based database tool */
-
-/** \cond some internal constants */
-#define MEDIUM_BLOB_SIZE 16777220 /* (2**24 + 4) */
-#define BLOB_SIZE 65539 /* (2**16 + 3) */
-/** \endcond */
-#include "server.cmdline.h"
-#include "server.h"
-#include "afs.h"
-#include "db.h"
-#include <mysql/mysql.h>
-#include <mysql/mysql_version.h>
-#include "error.h"
-#include "net.h"
-#include "string.h"
-
-extern struct gengetopt_args_info conf;
-/** pointer to the shared memory area */
-extern struct misc_meta_data *mmd;
-
-static void *mysql_ptr = NULL;
-
-
-static int com_cam(int, int, char **);
-static int com_cdb(int, int, char **);
-static int com_cs(int, int, char **);
-static int com_da(int, int, char **);
-static int com_hist(int, int, char **);
-static int com_info(int, int, char **);
-static int com_laa(int, int, char **);
-static int com_last(int, int, char **);
-static int com_ls(int, int, char **);
-static int com_mbox(int, int, char **);
-static int com_mv(int, int, char **);
-static int com_na(int, int, char **);
-static int com_pic(int, int, char **);
-static int com_picch(int, int, char **);
-static int com_picdel(int, int, char **);
-static int com_piclist(int, int, char **);
-static int com_ps(int, int, char **);
-static int com_rm_ne(int, int, char **);
-static int com_sa(int, int, char **);
-static int com_set(int, int, char **);
-static int com_sl(int, int, char **);
-static int com_stradd_picadd(int, int, char **);
-static int com_streams(int, int, char **);
-static int com_strdel(int, int, char **);
-static int com_strq(int, int, char **);
-static int com_summary(int, int, char **);
-static int com_upd(int, int, char **);
-static int com_us(int, int, char **);
-static int com_verb(int, int, char **);
-static int com_vrfy(int, int, char **);
-
-static struct server_command cmds[] = {
-{
-.name = "cam",
-.handler = com_cam,
-.perms = DB_READ|DB_WRITE,
-.description = "copy all metadata",
-.synopsis = "cam source dest1 [dest2 ...]",
-.help =
-
-"Copy attributes and other meta data from source file to destination\n"
-"file(s). Useful for files that have been renamed.\n"
-
-},
-{
-.name = "cdb",
-.handler = com_cdb,
-.perms = DB_READ|DB_WRITE,
-.description = "create database",
-.synopsis = "cdb [name]",
-.help =
-
-"\tCreate database name containing the initial columns for basic\n"
-"\tinteroperation with server. This command has to be used only once\n"
-"\twhen you use the mysql database tool for the very first time.\n"
-"\n"
-"\tThe optional name defaults to 'paraslash' if not given.\n"
-
-},
-{
-.name = "clean",
-.handler = com_vrfy,
-.perms = DB_READ | DB_WRITE,
-.description = "nuke invalid entries in database",
-.synopsis = "clean",
-.help =
-
-"If the vrfy command shows you any invalid entries in your database,\n"
-"you can get rid of them with clean. Always run 'upd' and 'vrfy'\n"
-"before running this command. Use with caution!\n"
-
-},
-{
-.name = "cs",
-.handler = com_cs,
-.perms = AFS_WRITE | DB_READ | DB_WRITE,
-.description = "change stream",
-.synopsis = "cs [s]",
-.help =
-
-"Selects stream s or prints current stream when s was not given.\n"
-
-},
-{
-.name = "csp",
-.handler = com_cs,
-.perms = AFS_WRITE | DB_READ,
-.description = "change stream and play",
-.synopsis = "csp s",
-.help =
-
-"Select stream s and start playing. If this results in a stream-change,\n"
-"skip rest of current audio file.\n"
-
-},
-{
-.name = "da",
-.handler = com_da,
-.perms = DB_READ | DB_WRITE,
-.description = "drop attribute from database",
-.synopsis = "da att",
-.help =
-
-"Use with caution. All info on attribute att will be lost.\n"
-
-},
-{
-.name = "hist",
-.handler = com_hist,
-.perms = DB_READ,
-.description = "print history",
-.synopsis = "hist",
-.help =
-
-"Print list of all audio files together with number of days since each\n"
-"file was last played.\n"
-
-},
-{
-.name = "info",
-.handler = com_info,
-.perms = DB_READ,
-.description = "print database info",
-.synopsis = "info [af]",
-.help =
-
-"print database informations for audio file af. Current audio file is\n"
-"used if af is not given.\n"
-
-},
-{
-.name = "la",
-.handler = com_info,
-.perms = DB_READ,
-.description = "list attributes",
-.synopsis = "la [af]",
-.help =
-
-"List attributes of audio file af or of current audio file when invoked\n"
-"without arguments.\n"
-
-},
-{
-.name = "laa",
-.handler = com_laa,
-.perms = DB_READ,
-.description = "list available attributes",
-.synopsis = "laa",
-.help =
-
-"What should I say more?\n"
-
-},
-{
-.name = "last",
-.handler = com_last,
-.perms = DB_READ,
-.description = "print list of audio files, ordered by lastplayed time",
-.synopsis = "last [n]",
-.help =
-
-"The optional number n defaults to 10 if not specified.\n"
-
-},
-{
-.name = "ls",
-.handler = com_ls,
-.perms = DB_READ,
-.description = "list all audio files that match a LIKE pattern",
-.synopsis = "ls [pattern]",
-.help =
-
-"\tIf pattern was not given, print list of all audio files known\n"
-"\tto the mysql database tool. See the documentation of mysql\n"
-"\tfor the definition of LIKE patterns.\n"
-
-},
-{
-.name = "mbox",
-.handler = com_mbox,
-.perms = DB_READ,
-.description = "dump audio file list in mbox format",
-.synopsis = "mbox [p]",
-.help =
-
-"\tDump list of audio files in mbox format (email) to stdout. If\n"
-"\tthe optional pattern p is given, only those audio files,\n"
-"\twhose basename match p are going to be included. Otherwise,\n"
-"\tall files are selected.\n"
-"\n"
-"EXAMPLE\n"
-"\tThe mbox command can be used together with your favorite\n"
-"\tmailer (this example uses mutt) for browsing the audio file\n"
-"\tcollection:\n"
-"\n"
-"\t\tpara_client mbox > ~/para_mbox\n"
-"\n"
-"\t\tmutt -F ~/.muttrc.para -f ~/para_mbox\n"
-"\n"
-"\tFor playlists, you can use mutt's powerful pattern matching\n"
-"\tlanguage to select files. If you like to tag all files\n"
-"\tcontaining the pattern 'foo', type 'T', then '~s foo'.\n"
-"\n"
-"\tWhen ready with the list, type ';|' (i.e., hit the semicolon\n"
-"\tkey to apply the next mutt command to all tagged messages,\n"
-"\tthen the pipe key) to pipe the selected \"mails\" to a\n"
-"\tsuitable script which adds a paraslash stream where exactly\n"
-"\tthese files are admissable or does whatever thou wilt.\n"
-
-},
-{
-.name = "mv",
-.handler = com_mv,
-.perms = DB_READ | DB_WRITE,
-.description = "rename entry in database",
-.synopsis = "mv oldname newname",
-.help =
-
-"Rename oldname to newname. This updates the data table to reflect the\n"
-"new name. All internal data (numplayed, lastplayed, picid,..) is kept.\n"
-"If newname is a full path, the dir table is updated as well.\n"
-
-},
-{
-.name = "na",
-.handler = com_na,
-.perms = DB_READ | DB_WRITE,
-.description = "add new attribute to database",
-.synopsis = "na att",
-.help =
-
-"This adds a column named att to your mysql database. att should only\n"
-"contain letters and numbers, in paricular, '+' and '-' are not allowed.\n"
-
-},
-{
-.name = "ne",
-.handler = com_rm_ne,
-.perms = DB_READ | DB_WRITE,
-.description = "add new database entries",
-.synopsis = "ne file1 [file2 [...]]",
-.help =
-
-"Add the given filename(s) to the database, where file1,... must\n"
-"be full path names. This command might be much faster than 'upd'\n"
-"if the number of given files is small.\n"
-
-},
-{
-.name = "ns",
-.handler = com_ps,
-.perms = AFS_WRITE | DB_READ | DB_WRITE,
-.description = "change to next stream",
-.synopsis = "ns",
-.help =
-
-"Cycle forwards through stream list.\n"
-
-},
-{
-.name = "pic",
-.handler = com_pic,
-.perms = DB_READ,
-.description = "get picture by name or by identifier",
-.synopsis = "pic [name]",
-.help =
-
-"\tDump jpg image that is associated to given audio file (current\n"
-"\taudio file if not specified) to stdout. If name starts with\n"
-"\t'#' it is interpreted as an identifier instead and the picture\n"
-"\thaving that identifier is dumped to stdout.\n"
-"\n"
-"EXAMPLE\n"
-"\n"
-"\tpara_client pic '#123' > pic123.jpg\n"
-
-},
-{
-.name = "picadd",
-.handler = com_stradd_picadd,
-.perms = DB_READ | DB_WRITE,
-.description = "add picture to database",
-.synopsis = "picadd [picname]",
-.help =
-
-"\tRead jpeg file from stdin and store it as picname in database.\n"
-"\n"
-"EXAMPLE\n"
-"\n"
-"\tpara_client picadd foo.jpg < foo.jpg\n"
-
-},
-{
-.name = "picass",
-.handler = com_set,
-.perms = DB_READ | DB_WRITE,
-.description = "associate a picture to file(s)",
-.synopsis = "picass pic_id file1 [file2...]",
-.help =
-
-"Associate the picture given by pic_id to all given files.\n"
-
-},
-{
-.name = "picch",
-.handler = com_picch,
-.perms = DB_READ | DB_WRITE,
-.description = "change name of picture",
-.synopsis = "picch id new_name",
-.help =
-
-"Asign new_name to picture with identifier id.\n"
-
-},
-{
-.name = "picdel",
-.handler = com_picdel,
-.perms = DB_READ | DB_WRITE,
-.description = "delete picture from database",
-.synopsis = "picdel id1 [id2...]",
-.help =
-
-"Delete each given picture from database.\n"
-
-},
-{
-.name = "piclist",
-.handler = com_piclist,
-.perms = DB_READ,
-.description = "print list of pictures",
-.synopsis = "piclist",
-.help =
-
-"Print id, name and length of each picture contained in the database.\n"
-
-},
-{
-.name = "ps",
-.handler = com_ps,
-.perms = AFS_WRITE | DB_READ | DB_WRITE,
-.description = "change to previous stream",
-.synopsis = "ps",
-.help =
-
-"Cycle backwards through stream list.\n"
-
-},
-{
-.name = "rm",
-.handler = com_rm_ne,
-.perms = DB_READ | DB_WRITE,
-.description = "remove entries from database",
-.synopsis = "rm name1 [name2 [...]]",
-.help =
-
-"Remove name1, name2, ... from the data table. Use with caution\n"
-
-},
-{
-.name = "sa",
-.handler = com_sa,
-.perms = DB_READ | DB_WRITE,
-.description = "set/unset attributes",
-.synopsis = "sa at1<'+' | '-'> [at2<'+' | '-'> ] [af1 ...]",
-//.synopsis = "foo",
-.help =
-
-"Set ('+') or unset ('-') attribute at1, at2 etc. for given list of\n"
-"audio files. If no audio files were given the current audio file is\n"
-"used. Example:\n"
-"\n"
-"sa rock+ punk+ classic- LZ__Waldsterben.mp3\n"
-"\n"
-"sets the 'rock' and the 'punk' attribute but unsets the 'classic'\n"
-"attribute.\n"
-
-},
-{
-.name = "skip",
-.handler = com_sl,
-.perms = DB_READ | DB_WRITE,
-.description = "skip subsequent audio files(s)",
-.synopsis = "skip n [s]",
-.help =
-
-"Skip the next n audio files of stream s. This is equivalent to the\n"
-"command 'sl n s', followed by 'us name' for each name the output of sl.\n"
-
-},
-{
-.name = "sl",
-.handler = com_sl,
-.perms = DB_READ,
-.description = "print score list",
-.synopsis = "sl n [s]",
-.help =
-
-"Print sorted list of maximal n lines. Each line is an admissible entry\n"
-"with respect to stream s. The list is sorted by score-value which is\n"
-"given by the definition of s. If s is not given, the current stream\n"
-"is used. Example:\n"
-"\n"
-" sl 1\n"
-"\n"
-"shows you the audio file the server would select right now.\n"
-
-},
-{
-.name = "snp",
-.handler = com_set,
-.perms = DB_READ | DB_WRITE,
-.description = "set numplayed",
-.synopsis = "snp number af1 [af2 ...]",
-.help =
-
-"Update the numplayed field in the data table for all given audio files.\n"
-
-},
-{
-.name = "stradd",
-.handler = com_stradd_picadd,
-.perms = DB_READ | DB_WRITE,
-.description = "add stream",
-.synopsis = "stradd s",
-.help =
-
-"Add stream s to the list of available streams. The stream definition\n"
-"for s is read from stdin and is then sent to para_server. Example:\n"
-"\n"
-" echo 'deny: NAME_LIKE(%Madonna%)' | para_client stradd no_madonna\n"
-"\n"
-"adds the new stream 'no_madonna' to the list of available streams. A given\n"
-"audio file is admissible for this stream iff its basename does not contain the\n"
-"string 'Madonna'.\n"
-
-
-},
-{
-.name = "strdel",
-.handler = com_strdel,
-.perms = DB_READ | DB_WRITE,
-.description = "delete stream",
-.synopsis = "strdel s",
-.help =
-
-"Remove stream s from database.\n"
-
-},
-{
-.name = "streams",
-.handler = com_streams,
-.perms = DB_READ,
-.description = "list streams",
-.synopsis = "streams",
-.help =
-
-"Print list of available streams. Use 'cs' to switch to any of these.\n"
-
-},
-{
-.name = "strq",
-.handler = com_strq,
-.perms = DB_READ,
-.description = "query stream definition",
-.synopsis = "strq [s]",
-.help =
-
-"Print definition of stream s to stdout. Use current stream if s was\n"
-"not given.\n"
-
-},
-{
-.name = "summary",
-.handler = com_summary,
-.perms = DB_READ,
-.description = "list attributes",
-.synopsis = "summary",
-.help =
-
-"\tPrint a list of attributes together with number of audio\n"
-"\tfiles having that attribute set.\n"
-
-},
-{
-.name = "upd",
-.handler = com_upd,
-.perms = DB_READ | DB_WRITE,
-.description = "update database",
-.synopsis = "upd",
-.help =
-
-"This command uses the --audio_file_dir option of para_server to locate\n"
-"your audio files. New files are then added to the mysql database. Use\n"
-"this command if you got new files or if you have moved some files\n"
-"around.\n"
-
-},
-{
-.name = "us",
-.handler = com_us,
-.perms = DB_READ | DB_WRITE,
-.description = "update lastplayed time",
-.synopsis = "us name",
-.help =
-
-"Update lastplayed time without actually playing the thing.\n"
-
-},
-{
-.name = "verb",
-.handler = com_verb,
-.perms = DB_READ | DB_WRITE,
-.description = "send verbatim sql query",
-.synopsis = "verb cmd",
-.help =
-
-"Send cmd to mysql server. For expert/debugging only. Note that cmd\n"
-"usually must be escaped. Use only if you know what you are doing!\n"
-
-},
-{
-.name = "vrfy",
-.handler = com_vrfy,
-.perms = DB_READ,
-.description = "list invalid entries in database",
-.synopsis = "vrfy",
-.help =
-
-"Show what clean would delete. Run 'upd' before this command to make\n"
-"sure your database is up to date.\n"
-
-},
-{
-.name = NULL,
-}
-};
-
-static struct para_macro macro_list[] = {
- { .name = "IS_N_SET",
- .replacement = "(data.%s != '1')"
- }, {
- .name = "IS_SET",
- .replacement = "(data.%s = '1')"
- }, {
- .name = "PICID",
- .replacement = "%sdata.Pic_Id"
- }, {
- .name = "NAME_LIKE",
- .replacement = "(data.name like '%s')"
- }, {
- .name = "LASTPLAYED",
- .replacement = "%sFLOOR((UNIX_TIMESTAMP(now())"
- "-UNIX_TIMESTAMP(data.Lastplayed))/60)"
- }, {
- .name = "NUMPLAYED",
- .replacement = "%sdata.Numplayed"
- }, {
- .name = NULL,
- }
-};
-
-static int real_query(char *query)
-{
- if (!mysql_ptr)
- return -E_NOTCONN;
- PARA_DEBUG_LOG("%s\n", query);
- if (mysql_real_query(mysql_ptr, query, strlen(query))) {
- PARA_ERROR_LOG("real_query error (%s)\n",
- mysql_error(mysql_ptr));
- return -E_QFAILED;
- }
- return 1;
-}
-
-/*
- * Use open connection given by mysql_ptr to query server. Returns a
- * result pointer on succes and NULL on errors
- */
-static struct MYSQL_RES *get_result(char *query)
-{
- void *result;
-
- if (real_query(query) < 0)
- return NULL;
- result = mysql_store_result(mysql_ptr);
- if (!result)
- PARA_ERROR_LOG("%s", "store_result error\n");
- return result;
-}
-/*
- * write input from fd to dynamically allocated char array,
- * but maximal max_size byte. Return size.
- */
-static int fd2buf(int fd, char **buf_ptr, size_t max_size)
-{
- const size_t chunk_size = 1024;
- size_t size = 2048;
- char *buf = para_malloc(size * sizeof(char)), *p = buf;
- int ret;
-
- while ((ret = recv_bin_buffer(fd, p, chunk_size)) > 0) {
- p += ret;
- if ((p - buf) + chunk_size >= size) {
- char *tmp;
-
- size *= 2;
- if (size > max_size) {
- ret = -E_TOOBIG;
- goto out;
- }
- tmp = para_realloc(buf, size);
- p = (p - buf) + tmp;
- buf = tmp;
- }
- }
- if (ret < 0)
- goto out;
- *buf_ptr = buf;
- ret = p - buf;
-out:
- if (ret < 0 && buf)
- free(buf);
- return ret;
-}
-
-static char *escape_blob(char* old, int size)
-{
- char *new;
-
- if (!mysql_ptr || size < 0)
- return NULL;
- new = para_malloc(2 * size * sizeof(char) + 1);
- mysql_real_escape_string(mysql_ptr, new, old, size);
- return new;
-}
-
-static char *escape_str(char* old)
-{
- return escape_blob(old, strlen(old));
-}
-
-static char *escaped_basename(const char *name)
-{
- char *esc, *bn = para_basename(name);
-
- if (!bn)
- return NULL;
- esc = escape_str(bn);
- free(bn);
- return esc;
-}
-
-/*
- * new attribute
- */
-static int com_na(__unused int fd, int argc, char *argv[])
-{
- char *q;
- int ret;
-
- if (argc < 1)
- return -E_MYSQL_SYNTAX;
- q = make_message("alter table data add %s char(1) "
- "not null default 0", argv[1]);
- ret = real_query(q);
- free(q);
- return ret;
-}
-
-/*
- * delete attribute
- */
-static int com_da(__unused int fd, int argc, char *argv[])
-{
- char *q;
- int ret;
-
- if (argc < 1)
- return -E_MYSQL_SYNTAX;
- q = make_message("alter table data drop %s", argv[1]);
- ret = real_query(q);
- free(q);
- return ret;
-}
-
-/* stradd/pic_add */
-static int com_stradd_picadd(int fd, int argc, char *argv[])
-{
- char *blob = NULL, *esc_blob = NULL, *q;
- const char *fmt, *del_fmt;
- int ret, stradd = strcmp(argv[0], "picadd");
- size_t size;
-
- if (argc < 1)
- return -E_MYSQL_SYNTAX;
- if (strlen(argv[1]) >= MAXLINE - 1)
- return -E_NAMETOOLONG;
- if (!mysql_ptr)
- return -E_NOTCONN;
- if (stradd) {
- size = BLOB_SIZE;
- fmt = "insert into streams (name, def) values ('%s','%s')";
- del_fmt="delete from streams where name='%s'";
- } else {
- size = MEDIUM_BLOB_SIZE;
- fmt = "insert into pics (name, pic) values ('%s','%s')";
- del_fmt="delete from pics where pic='%s'";
- }
- q = make_message(del_fmt, argv[1]);
- ret = real_query(q);
- free(q);
- if (ret < 0)
- return ret;
- if ((ret = send_buffer(fd, AWAITING_DATA_MSG) < 0))
- return ret;
- if ((ret = fd2buf(fd, &blob, size)) < 0)
- return ret;
- PARA_DEBUG_LOG("length: %i\n", ret);
- size = ret;
- if (stradd)
- blob[size] = '\0';
- esc_blob = escape_blob(blob, ret);
- free(blob);
- if (!esc_blob)
- return -E_TOOBIG;
- q = make_message(fmt, argv[1], esc_blob);
- free(esc_blob);
- ret = real_query(q);
- free(q);
- return ret;
-}
-
-/*
- * print results to fd
- */
-static int print_results(int fd, void *result,
- unsigned int top, unsigned int left,
- unsigned int bottom, unsigned int right)
-{
- unsigned int i,j;
- int ret;
- MYSQL_ROW row;
-
- for (i = top; i <= bottom; i++) {
- row = mysql_fetch_row(result);
- if (!row || !row[0])
- return -E_NOROW;
- for (j = left; j <= right; j++) {
- ret = send_va_buffer(fd, j == left? "%s" : "\t%s",
- row[j]? row[j] : "NULL");
- if (ret < 0)
- return ret;
- }
- ret = send_buffer(fd, "\n");
- if (ret < 0)
- return ret;
- }
- return 0;
-}
-
-/*
- * verbatim
- */
-static int com_verb(int fd, int argc, char *argv[])
-{
- void *result = NULL;
- int ret;
- unsigned int num_rows, num_fields;
-
- if (argc < 1)
- return -E_MYSQL_SYNTAX;
- result = get_result(argv[1]);
- if (!result)
- /* return success, because it's ok to have no results */
- return 1;
- num_fields = mysql_field_count(mysql_ptr);
- num_rows = mysql_num_rows(result);
- ret = 1;
- if (num_fields && num_rows)
- ret = print_results(fd, result, 0, 0, num_rows - 1,
- num_fields - 1);
- mysql_free_result(result);
- return ret;
-}
-
-/* returns NULL on errors or if there are no atts defined yet */
-static void *get_all_attributes(void)
-{
- void *result = get_result("desc data");
- unsigned int num_rows;
-
- if (!result)
- return NULL;
- num_rows = mysql_num_rows(result);
- if (num_rows < 5) {
- mysql_free_result(result);
- return NULL;
- }
- mysql_data_seek(result, 4); /* skip Lastplayed, Numplayed... */
- return result;
-}
-
-/*
- * list all attributes
- */
-static int com_laa(int fd, int argc, __unused char *argv[])
-{
- void *result;
- int ret;
-
- if (argc)
- return -E_MYSQL_SYNTAX;
- result = get_all_attributes();
- if (!result)
- return -E_NOATTS;
- ret = print_results(fd, result, 0, 0, mysql_num_rows(result) - 5, 0);
- mysql_free_result(result);
- return ret;
-}
-
-/*
- * history
- */
-static int com_hist(int fd, int argc, char *argv[]) {
- int ret;
- void *result = NULL;
- char *q;
- unsigned int num_rows;
-
- q = make_message("select name, to_days(now()) - to_days(lastplayed) from "
- "data%s%s%s order by lastplayed",
- (argc < 1)? "" : " where ",
- (argc < 1)? "" : argv[1],
- (argc < 1)? "" : " = '1'");
- result = get_result(q);
- free(q);
- if (!result)
- return -E_NORESULT;
- num_rows = mysql_num_rows(result);
- ret = 1;
- if (num_rows)
- ret = print_results(fd, result, 0, 0, num_rows - 1, 1);
- mysql_free_result(result);
- return ret;
-}
-
-/*
- * get last num audio files
- */
-static int com_last(int fd, int argc, char *argv[])
-{
- void *result = NULL;
- char *q;
- int num, ret;
-
- if (argc < 1)
- num = 10;
- else
- num = atoi(argv[1]);
- if (!num)
- return -E_MYSQL_SYNTAX;
- q = make_message("select name from data order by lastplayed desc "
- "limit %u", num);
- result = get_result(q);
- free(q);
- if (!result)
- return -E_NORESULT;
- ret = print_results(fd, result, 0, 0, mysql_num_rows(result) - 1, 0);
- mysql_free_result(result);
- return ret;
-}
-
-static int com_mbox(int fd, int argc, char *argv[])
-{
- void *result;
- MYSQL_ROW row;
- int ret;
- unsigned int num_rows, num_fields;
- char *query = para_strdup("select concat('From foo@localhost ', "
- "date_format(Lastplayed, '%a %b %e %T %Y'), "
- "'\nReceived: from\nTo: bar\n");
-
- ret = -E_NOATTS;
- result = get_all_attributes();
- if (!result)
- goto out;
- ret = -E_NOROW;
- while ((row = mysql_fetch_row(result))) {
- char *tmp;
-
- if (!row[0])
- goto out;
- tmp = make_message("%s X-Attribute-%s: ', %s, '\n", query,
- row[0], row[0]);
- free(query);
- query = tmp;
- }
- query = para_strcat(query,
- "From: a\n"
- "Subject: "
- "', name, '"
- "\n\n\n"
- "') from data"
- );
- if (argc >= 1) {
- char *tmp = make_message("%s where name LIKE '%s'", query,
- argv[1]);
- free(query);
- query = tmp;
- }
- mysql_free_result(result);
- ret = -E_NORESULT;
- result = get_result(query);
- if (!result)
- goto out;
- ret = -E_EMPTY_RESULT;
- num_fields = mysql_field_count(mysql_ptr);
- num_rows = mysql_num_rows(result);
- if (!num_fields || !num_rows)
- goto out;
- ret = print_results(fd, result, 0, 0, num_rows - 1, num_fields - 1);
-out:
- free(query);
- if (result)
- mysql_free_result(result);
- return ret;
-}
-
-/* get attributes by name. If verbose is not 0, get_a writes a string
- * into atts of the form 'att1="0",att2="1"', which is used in com_cam
- * for contructing a mysql update query.
- * never returns NULL in *NON VERBOSE* mode
- */
-static char *get_atts(char *name, int verbose)
-{
- char *atts = NULL, *buf, *ebn;
- void *result = NULL, *result2 = NULL;
- MYSQL_ROW row, row2;
- int i, ret;
- unsigned int num_fields;
-
- ret = -E_NOATTS;
- result2 = get_all_attributes();
- if (!result2)
- goto out;
- ret = -E_ESCAPE;
- if (!(ebn = escaped_basename(name)))
- goto out;
- buf = make_message("select * from data where name='%s'", ebn);
- free(ebn);
- ret = -E_NORESULT;
- result = get_result(buf);
- free(buf);
- if (!result)
- goto out;
- ret = -E_EMPTY_RESULT;
- num_fields = mysql_num_fields(result);
- if (num_fields < 5)
- goto out;
- mysql_data_seek(result2, 4); /* skip Lastplayed, Numplayed... */
- row = mysql_fetch_row(result);
- ret = -E_NOROW;
- if (!row)
- goto out;
- for (i = 4; i < num_fields; i++) {
- int is_set = row[i] && !strcmp(row[i], "1");
- row2 = mysql_fetch_row(result2);
- if (!row2 || !row2[0])
- goto out;
- if (atts && (verbose || is_set))
- atts = para_strcat(atts, verbose? "," : " ");
- if (is_set || verbose)
- atts = para_strcat(atts, row2[0]);
- if (verbose)
- atts = para_strcat(atts, is_set? "=\"1\"" : "=\"0\"");
- }
- ret = 1;
-out:
- if (result2)
- mysql_free_result(result2);
- if (result)
- mysql_free_result(result);
- if (!atts && !verbose)
- atts = para_strdup("(none)");
- return atts;
-}
-
-/* never returns NULL in verbose mode */
-static char *get_meta(char *name, int verbose)
-{
- MYSQL_ROW row;
- void *result = NULL;
- char *ebn, *q, *ret = NULL;
- const char *verbose_fmt =
- "select concat('lastplayed: ', "
- "(to_days(now()) - to_days(lastplayed)),"
- "' day(s). numplayed: ', numplayed, "
- "', pic: ', pic_id) "
- "from data where name = '%s'";
- /* is that really needed? */
- const char *fmt = "select concat('lastplayed=\\'', lastplayed, "
- "'\\', numplayed=\\'', numplayed, "
- "'\\', pic_id=\\'', pic_id, '\\'') "
- "from data where name = '%s'";
-
- if (!(ebn = escaped_basename(name)))
- goto out;
- q = make_message(verbose? verbose_fmt : fmt, ebn);
- free(ebn);
- result = get_result(q);
- free(q);
- if (!result)
- goto out;
- row = mysql_fetch_row(result);
- if (!row || !row[0])
- goto out;
- ret = para_strdup(row[0]);
-out:
- if (result)
- mysql_free_result(result);
- if (!ret && verbose)
- ret = para_strdup("(not yet played)");
- return ret;
-}
-
-static char *get_dir(char *name)
-{
- char *ret = NULL, *q, *ebn;
- void *result;
- MYSQL_ROW row;
-
- if (!(ebn = escaped_basename(name)))
- return NULL;
- q = make_message("select dir from dir where name = '%s'", ebn);
- free(ebn);
- result = get_result(q);
- free(q);
- if (!result)
- return NULL;
- row = mysql_fetch_row(result);
- if (row && row[0])
- ret = para_strdup(row[0]);
- mysql_free_result(result);
- return ret;
-}
-
-/* never returns NULL */
-static char *get_current_stream(void)
-{
- char *ret;
- MYSQL_ROW row;
- void *result = get_result("select def from streams where "
- "name = 'current_stream'");
-
- if (!result)
- goto err_out;
- row = mysql_fetch_row(result);
- if (!row || !row[0])
- goto err_out;
- ret = para_strdup(row[0]);
- mysql_free_result(result);
- return ret;
-err_out:
- if (result)
- mysql_free_result(result);
- return para_strdup("(none)");
-}
-/*
- * Read stream definition of stream streamname and construct mysql
- * query. Return NULL on errors. If streamname is NULL, use current
- * stream. If that is also NULL, use query that selects everything.
- * If filename is NULL, query will list everything, otherwise only
- * the score of given file.
- */
-static char *get_query(char *streamname, char *filename, int with_path)
-{
- char *accept_opts = NULL, *deny_opts = NULL, *score = NULL;
- char *where_clause, *order, *query;
- char command[255] = ""; /* buffer for sscanf */
- void *result;
- MYSQL_ROW row;
- char *end, *tmp;
- char *select_clause = NULL;
- if (!streamname)
- tmp = get_current_stream();
- else
- tmp = para_strdup(streamname);
- if (!strcmp(tmp, "(none)")) {
- free(tmp);
- if (filename) {
- char *ret, *ebn = escaped_basename(filename);
- ret = make_message("select to_days(now()) - "
- "to_days(lastplayed) from data "
- "where name = '%s'", ebn);
- free(ebn);
- return ret;
- }
- if (with_path)
- return make_message(
- "select concat(dir.dir, '/', dir.name) "
- "from data, dir where dir.name = data.name "
- "order by data.lastplayed"
- );
- return make_message(
- "select name from data where name is not NULL "
- "order by lastplayed"
- );
- }
- free(tmp);
- query = make_message("select def from streams where name = '%s'",
- streamname);
- result = get_result(query);
- free(query);
- query = NULL;
- if (!result)
- goto out;
- row = mysql_fetch_row(result);
- if (!row || !row[0])
- goto out;
- end = row[0];
- while (*end) {
- int n;
- char *arg, *line = end;
-
- if (!(end = strchr(line, '\n')))
- break;
- *end = '\0';
- end++;
- if (sscanf(line, "%200s%n", command, &n) < 1)
- continue;
- arg = line + n;
- if (!strcmp(command, "accept:")) {
- char *tmp2 = s_a_r_list(macro_list, arg);
- if (accept_opts)
- accept_opts = para_strcat(
- accept_opts, " or ");
- accept_opts = para_strcat(accept_opts, tmp2);
- free(tmp2);
- continue;
- }
- if (!strcmp(command, "deny:")) {
- char *tmp2 = s_a_r_list(macro_list, arg);
- if (deny_opts)
- deny_opts = para_strcat(deny_opts, " or ");
- deny_opts = para_strcat(deny_opts, tmp2);
- free(tmp2);
- continue;
- }
- if (!strcmp(command, "score:"))
- score = s_a_r_list(macro_list, arg);
- }
- if (!score) {
- score = s_a_r_list(macro_list, conf.mysql_default_score_arg);
- if (!score)
- goto out;
- }
- if (filename) {
- char *ebn = escaped_basename(filename);
- if (!ebn)
- goto out;
- select_clause = make_message("select %s from data ", score);
- free(score);
- where_clause = make_message( "where name = '%s' ", ebn);
- free(ebn);
- order = para_strdup("");
- goto write_query;
- }
- select_clause = para_strdup(with_path?
- "select concat(dir.dir, '/', dir.name) from data, dir "
- "where dir.name = data.name "
- :
- "select name from data where name is not NULL");
- order = make_message("order by -(%s)", score);
- free(score);
- if (accept_opts && deny_opts) {
- where_clause = make_message("and ((%s) and not (%s)) ",
- accept_opts, deny_opts);
- goto write_query;
- }
- if (accept_opts && !deny_opts) {
- where_clause = make_message("and (%s) ", accept_opts);
- goto write_query;
- }
- if (!accept_opts && deny_opts) {
- where_clause = make_message("and not (%s) ", deny_opts);
- goto write_query;
- }
- where_clause = para_strdup("");
-write_query:
- query = make_message("%s %s %s", select_clause, where_clause, order);
- free(order);
- free(select_clause);
- free(where_clause);
-out:
- if (accept_opts)
- free(accept_opts);
- if (deny_opts)
- free(deny_opts);
- if (result)
- mysql_free_result(result);
- return query;
-}
-
-
-
-/*
- * This is called from server and from some commands. Name must not be NULL
- * Never returns NULL.
- */
-static char *get_dbinfo(char *name)
-{
- char *meta = NULL, *atts = NULL, *info, *dir = NULL, *query, *stream = NULL;
- void *result = NULL;
- MYSQL_ROW row = NULL;
-
- if (!name)
- return para_strdup("(none)");
- stream = get_current_stream();
- meta = get_meta(name, 1);
- atts = get_atts(name, 0);
- dir = get_dir(name);
- /* get score */
- query = get_query(stream, name, 0);
- if (!query)
- goto write;
- result = get_result(query);
- free(query);
- if (result)
- row = mysql_fetch_row(result);
-write:
- info = make_message("dbinfo1:dir: %s\n"
- "dbinfo2:stream: %s, %s, score: %s\n"
- "dbinfo3:%s\n",
- dir? dir : "(not contained in table)",
- stream, meta,
- (result && row && row[0])? row[0] : "(no score)",
- atts);
- if (dir)
- free(dir);
- if (meta)
- free(meta);
- if (atts)
- free(atts);
- if (stream)
- free(stream);
- if (result)
- mysql_free_result(result);
- return info;
-}
-
-
-/* might return NULL */
-static char *get_current_audio_file(void)
-{
- char *name;
- mmd_lock();
- name = para_basename(mmd->filename);
- mmd_unlock();
- return name;
-}
-
-
-/* print database info */
-static int com_info(int fd, int argc, char *argv[])
-{
- char *name = NULL, *meta = NULL, *atts = NULL, *dir = NULL;
- int ret, com_la = strcmp(argv[0], "info");
-
- if (argc < 1) {
- ret = -E_GET_AUDIO_FILE;
- if (!(name = get_current_audio_file()))
- goto out;
- ret = send_va_buffer(fd, "%s\n", name);
- if (ret < 0)
- goto out;
- } else {
- ret = -E_ESCAPE;
- if (!(name = escaped_basename(argv[1])))
- goto out;
- }
- meta = get_meta(name, 1);
- atts = get_atts(name, 0);
- dir = get_dir(name);
- if (com_la)
- ret = send_va_buffer(fd, "%s\n", atts);
- else
- ret = send_va_buffer(fd, "dir: %s\n" "%s\n" "attributes: %s\n",
- dir? dir : "(not contained in table)", meta, atts);
-out:
- if (meta)
- free(meta);
- if (atts)
- free(atts);
- if (dir)
- free(dir);
- if (name)
- free(name);
- return ret;
-}
-static int change_stream(char *stream)
-{
- char *query;
- int ret;
- /* try to insert if it does not exist (compatibility) */
-// query = make_message("insert into streams (name, def) values "
-// "('current_stream', '%s')", stream);
-// real_query(query); /* ignore return value */
-// free(query);
- query = make_message("update streams set def='%s' "
- "where name = 'current_stream'", stream);
- ret = real_query(query);
- free(query);
- return ret;
-}
-
-static int get_pic_id_by_name(char *name)
-{
- char *q, *ebn;
- void *result = NULL;
- long unsigned ret;
- MYSQL_ROW row;
-
- if (!(ebn = escaped_basename(name)))
- return -E_ESCAPE;
- q = make_message("select pic_id from data where name = '%s'", ebn);
- free(ebn);
- result = get_result(q);
- free(q);
- if (!result)
- return -E_NORESULT;
- row = mysql_fetch_row(result);
- ret = -E_NOROW;
- if (row && row[0])
- ret = atol(row[0]);
- mysql_free_result(result);
- return ret;
-}
-
-static int remove_entry(const char *name)
-{
- char *q, *ebn = escaped_basename(name);
- int ret = -E_ESCAPE;
-
- if (!ebn || !*ebn)
- goto out;
- q = make_message("delete from data where name = '%s'", ebn);
- real_query(q); /* ignore errors */
- free(q);
- q = make_message("delete from dir where name = '%s'", ebn);
- real_query(q); /* ignore errors */
- free(q);
- ret = 1;
-out:
- free(ebn);
- return ret;
-}
-
-static int add_entry(const char *name)
-{
- char *q, *dn, *ebn = NULL, *edn = NULL;
- int ret;
-
- if (!name || !*name)
- return -E_MYSQL_SYNTAX;
- ebn = escaped_basename(name);
- if (!ebn)
- return -E_ESCAPE;
- ret = -E_MYSQL_SYNTAX;
- dn = para_dirname(name);
- if (!dn)
- goto out;
- ret = -E_ESCAPE;
- edn = escape_str(dn);
- free(dn);
- if (!edn || !*edn)
- goto out;
- q = make_message("insert into data (name, pic_id) values "
- "('%s', '%s')", ebn, "1");
- ret = real_query(q);
-// ret = 1; PARA_DEBUG_LOG("q: %s\n", q);
- free(q);
- if (ret < 0)
- goto out;
- q = make_message("insert into dir (name, dir) values "
- "('%s', '%s')", ebn, edn);
-// ret = 1; PARA_DEBUG_LOG("q: %s\n", q);
- ret = real_query(q);
- free(q);
-out:
- if (ebn)
- free(ebn);
- if (edn)
- free(edn);
- return ret;
-}
-
-/*
- * remove/add entries
- */
-static int com_rm_ne(__unused int fd, int argc, char *argv[])
-{
- int ne = !strcmp(argv[0], "ne");
- int i, ret;
- if (argc < 1)
- return -E_MYSQL_SYNTAX;
- for (i = 1; i <= argc; i++) {
- ret = remove_entry(argv[i]);
- if (ret < 0)
- return ret;
- if (!ne)
- continue;
- ret = add_entry(argv[i]);
- if (ret < 0)
- return ret;
- }
- return 1;
-}
-
-/*
- * mv: rename entry
- */
-static int com_mv(__unused int fd, int argc, char *argv[])
-{
- char *q, *dn, *ebn1 = NULL, *ebn2 = NULL, *edn = NULL;
- int ret;
-
- if (argc != 2)
- return -E_MYSQL_SYNTAX;
- ebn1 = escaped_basename(argv[1]);
- ebn2 = escaped_basename(argv[2]);
- dn = para_dirname(argv[2]);
- edn = escape_str(dn);
- free(dn);
- ret = -E_ESCAPE;
- if (!ebn1 || !ebn2)
- goto out;
- remove_entry(ebn2);
- q = make_message("update data set name = '%s' where name = '%s'",
- ebn2, ebn1);
- ret = real_query(q);
- free(q);
- if (ret < 0)
- goto out;
- q = make_message("update dir set name = '%s' where name = '%s'",
- ebn2, ebn1);
- ret = real_query(q);
- free(q);
- if (ret < 0)
- goto out;
- /* do not touch table dir, return success if argv[2] is no full path */
- ret = 1;
- if (!edn || !*edn)
- goto out;
- q = make_message("update dir set dir = '%s' where name = '%s'",
- edn, ebn2);
-// PARA_DEBUG_LOG("q: %s\n", q);
- ret = real_query(q);
- free(q);
-out:
- if (ebn1)
- free(ebn1);
- if (ebn2)
- free(ebn2);
- if (edn)
- free(edn);
- return ret;
-
-}
-
-/*
- * picass: associate pic to audio file
- * snp: set numplayed
- */
-static int com_set(__unused int fd, int argc, char *argv[])
-{
- char *q, *ebn;
- long unsigned id;
- int i, ret;
- char *field = strcmp(argv[0], "picass")? "numplayed" : "pic_id";
-
- if (argc < 2)
- return -E_MYSQL_SYNTAX;
- id = atol(argv[1]);
- for (i = 2; i <= argc; i++) {
- ebn = escaped_basename(argv[i]);
- if (!ebn)
- return -E_ESCAPE;
- q = make_message("update data set %s = %lu "
- "where name = '%s'", field, id, ebn);
- free(ebn);
- ret = real_query(q);
- free(q);
- if (ret < 0)
- return ret;
- }
- return 1;
-}
-
-/*
- * picch: change entry's name in pics table
- */
-static int com_picch(__unused int fd, int argc, char *argv[])
-{
- int ret;
- long unsigned id;
- char *q;
-
- if (argc != 2)
- return -E_MYSQL_SYNTAX;
- id = atol(argv[1]);
- if (strlen(argv[2]) > MAXLINE)
- return -E_NAMETOOLONG;
- q = make_message("update pics set name = '%s' where id = %lu", argv[2], id);
- ret = real_query(q);
- free(q);
- return ret;
-}
-
-/*
- * piclist: print list of pics in db
- */
-static int com_piclist(__unused int fd, int argc, __unused char *argv[])
-{
- void *result = NULL;
- MYSQL_ROW row;
- unsigned long *length;
- int ret;
-
- if (argc)
- return -E_MYSQL_SYNTAX;
- result = get_result("select id,name,pic from pics order by id");
- if (!result)
- return -E_NORESULT;
- while ((row = mysql_fetch_row(result))) {
- length = mysql_fetch_lengths(result);
- if (!row || !row[0] || !row[1] || !row[2])
- continue;
- ret = send_va_buffer(fd, "%s\t%lu\t%s\n", row[0], length[2], row[1]);
- if (ret < 0)
- goto out;
- }
- ret = 1;
-out:
- mysql_free_result(result);
- return ret;
-}
-
-/*
- * picdel: delete picture from database
- */
-static int com_picdel(int fd, int argc, char *argv[])
-{
- char *q;
- long unsigned id;
- my_ulonglong aff;
- int i, ret;
-
- if (argc < 1)
- return -E_MYSQL_SYNTAX;
- for (i = 1; i <= argc; i++) {
- id = atol(argv[i]);
- q = make_message("delete from pics where id = %lu", id);
- ret = real_query(q);
- free(q);
- if (ret < 0)
- return ret;
- aff = mysql_affected_rows(mysql_ptr);
- if (!aff) {
- ret = send_va_buffer(fd, "No such id: %lu\n", id);
- if (ret < 0)
- return ret;
- continue;
- }
- q = make_message("update data set pic_id = 1 where pic_id = %lu", id);
- ret = real_query(q);
- free(q);
- }
- return 1;
-}
-/*
- * pic: get picture by name or by number
- */
-static int com_pic(int fd, int argc, char *argv[])
-{
- void *result = NULL;
- MYSQL_ROW row;
- unsigned long *length, id;
- int ret;
- char *q, *name = NULL;
-
- if (argc < 1) {
- ret = -E_GET_AUDIO_FILE;
- name = get_current_audio_file();
- } else {
- ret = -E_ESCAPE;
- name = escaped_basename(argv[1]);
- }
- if (!name)
- return ret;
- if (*name == '#')
- id = atoi(name + 1);
- else
- id = get_pic_id_by_name(name);
- free(name);
- if (id <= 0)
- return id;
- q = make_message("select pic from pics where id = '%lu'", id);
- result = get_result(q);
- free(q);
- if (!result)
- return -E_NORESULT;
- row = mysql_fetch_row(result);
- ret = -E_NOROW;
- if (!row || !row[0])
- goto out;
- length = mysql_fetch_lengths(result);
- ret = send_bin_buffer(fd, row[0], *length);
-out:
- mysql_free_result(result);
- return ret;
-}
-
-/* strdel */
-static int com_strdel(__unused int fd, int argc, char *argv[])
-{
- char *tmp;
- int ret = -1;
-
- if (argc < 1)
- return -E_MYSQL_SYNTAX;
- tmp = make_message("delete from streams where name='%s'", argv[1]);
- ret = real_query(tmp);
- free(tmp);
- if (ret < 0)
- return ret;
- tmp = get_current_stream();
- ret = 1;
- if (strcmp(tmp, "(none)") && !strcmp(tmp, argv[1]))
- ret = change_stream("(none)");
- return ret;
-}
-
-/*
- * ls
- */
-static int com_ls(int fd, int argc, char *argv[])
-{
- char *q;
- void *result;
- int ret;
- unsigned int num_rows;
-
- if (argc > 0)
- q = make_message("select name from data where name LIKE '%s'",
- argv[1]);
- else
- q = para_strdup("select name from data");
- result = get_result(q);
- free(q);
- if (!result)
- return -E_NORESULT;
- num_rows = mysql_num_rows(result);
- ret = 1;
- if (num_rows)
- ret = print_results(fd, result, 0, 0, num_rows - 1, 0);
- mysql_free_result(result);
- return ret;
-}
-/*
- * summary
- */
-static int com_summary(__unused int fd, int argc, __unused char *argv[])
-{
- MYSQL_ROW row;
- MYSQL_ROW row2;
- void *result;
- void *result2 = NULL;
- const char *fmt = "select count(name) from data where %s='1'";
- int ret = -E_NORESULT;
-
- if (argc)
- return -E_MYSQL_SYNTAX;
- result = get_all_attributes();
- if (!result)
- goto out;
- while ((row = mysql_fetch_row(result))) {
- char *buf;
-
- ret = -E_NOROW;
- if (!row[0])
- goto out;
- ret = -E_NORESULT;
- buf = make_message(fmt, row[0]);
- result2 = get_result(buf);
- free(buf);
- if (!result2)
- goto out;
- ret = -E_NOROW;
- row2 = mysql_fetch_row(result2);
- if (!row2 || !row2[0])
- goto out;
- ret = send_va_buffer(fd, "%s\t%s\n", row[0], row2[0]);
- if (ret < 0)
- goto out;
- }
- ret = 1;
-out:
- if (result2)
- mysql_free_result(result2);
- if (result)
- mysql_free_result(result);
- return ret;
-}
-
-static int get_numplayed(char *name)
-{
- void *result;
- MYSQL_ROW row;
- const char *fmt = "select numplayed from data where name = '%s'";
- char *buf = make_message(fmt, name);
- int ret = -E_NORESULT;
-
- result = get_result(buf);
- free(buf);
- if (!result)
- goto out;
- ret = -E_NOROW;
- row = mysql_fetch_row(result);
- if (!row || !row[0])
- goto out;
- ret = atoi(row[0]);
-out:
- if (result)
- mysql_free_result(result);
- return ret;
-}
-
-static int update_audio_file(char *name)
-{
- int ret;
- const char *fmt1 = "update data set lastplayed = now() where name = '%s'";
- const char *fmt2 = "update data set numplayed = %i where name = '%s'";
- char *q;
- char *ebn = escaped_basename(name);
-
- ret = -E_ESCAPE;
- if (!ebn)
- goto out;
- q = make_message(fmt1, ebn);
- ret = real_query(q);
- free(q);
- if (ret < 0)
- goto out;
- ret = get_numplayed(ebn);
- if (ret < 0)
- goto out;
- q = make_message(fmt2, ret + 1, ebn);
- ret = real_query(q);
- free(q);
-out:
- if (ebn)
- free(ebn);
- return ret;
-}
-/* If called as child, mmd_lock must be held */
-static void update_mmd(char *info)
-{
- PARA_DEBUG_LOG("%s", "updating shared memory area\n");
- strncpy(mmd->dbinfo, info, MMD_INFO_SIZE - 1);
- mmd->dbinfo[MMD_INFO_SIZE - 1] = '\0';
-}
-
-static void update_audio_file_server_handler(char *name)
-{
- char *info;
- info = get_dbinfo(name);
- update_mmd(info);
- free(info);
- update_audio_file(name);
-}
-
-static int com_us(__unused int fd, int argc, char *argv[])
-{
- if (argc != 1)
- return -E_MYSQL_SYNTAX;
- return update_audio_file(argv[1]);
-}
-
-static void refresh_mmd_dbinfo(void)
-{
- char *name = get_current_audio_file();
- char *info;
-
- if (!name)
- return;
- info = get_dbinfo(name);
- free(name);
- mmd_lock();
- update_mmd(info);
- mmd_unlock();
- free(info);
-}
-
-/* select previous/next stream */
-static int com_ps(__unused int fd, int argc, char *argv[])
-{
- char *query, *stream = get_current_stream();
- void *result = get_result("select name from streams");
- MYSQL_ROW row;
- int match = -1, ret, i;
- unsigned int num_rows;
-
- if (argc)
- return -E_MYSQL_SYNTAX;
- ret = -E_NORESULT;
- if (!result)
- goto out;
- num_rows = mysql_num_rows(result);
- ret = -E_EMPTY_RESULT;
- if (num_rows < 2)
- goto out;
- ret = -E_NOROW;
- for (i = 0; i < num_rows; i++) {
- row = mysql_fetch_row(result);
- if (!row || !row[0])
- goto out;
- if (!strcmp(row[0], "current_stream"))
- continue;
- if (!strcmp(row[0], stream)) {
- match = i;
- break;
- }
- }
- ret = -E_NO_STREAM;
- if (match < 0)
- goto out;
- if (!strcmp(argv[0], "ps"))
- i = match > 0? match - 1 : num_rows - 1;
- else
- i = match < num_rows - 1? match + 1 : 0;
- ret = -E_NOROW;
- mysql_data_seek(result, i);
- row = mysql_fetch_row(result);
- if (!row || !row[0])
- goto out;
- if (!strcmp(row[0], "current_stream")) {
- if (!strcmp(argv[0], "ps")) {
- i = match - 2;
- i = i < 0? i + num_rows : i;
- } else {
- i = match + 2;
- i = i > num_rows - 1? i - num_rows : i;
- }
- mysql_data_seek(result, i);
- row = mysql_fetch_row(result);
- if (!row || !row[0])
- goto out;
- }
- query = make_message("update streams set def='%s' where name = "
- "'current_stream'", row[0]);
- ret = real_query(query);
- free(query);
- refresh_mmd_dbinfo();
-out:
- free(stream);
- if (result)
- mysql_free_result(result);
- return ret;
-}
-
-/* streams */
-static int com_streams(int fd, int argc, __unused char *argv[])
-{
- unsigned int num_rows;
- int i, ret = -E_NORESULT;
- void *result;
- MYSQL_ROW row;
-
- if (argc && strcmp(argv[1], "current_stream"))
- return -E_MYSQL_SYNTAX;
- if (argc) {
- char *cs = get_current_stream();
- ret = send_va_buffer(fd, "%s\n", cs);
- free(cs);
- return ret;
- }
- result = get_result("select name from streams");
- if (!result)
- goto out;
- num_rows = mysql_num_rows(result);
- ret = 1;
- if (!num_rows)
- goto out;
- ret = -E_NOROW;
- for (i = 0; i < num_rows; i++) {
- row = mysql_fetch_row(result);
- if (!row || !row[0])
- goto out;
- if (strcmp(row[0], "current_stream"))
- send_va_buffer(fd, "%s\n", row[0]);
- }
- ret = 1;
-out:
- if (result)
- mysql_free_result(result);
- return ret;
-}
-
-/* query stream definition */
-static int com_strq(int fd, int argc, char *argv[])
-{
- MYSQL_ROW row;
- char *query, *name;
- void *result;
- int ret;
-
- if (argc < 1) {
- ret = -E_GET_STREAM;
- name = get_current_stream();
- } else {
- ret = -E_ESCAPE;
- name = escaped_basename(argv[1]);
- }
- if (!name)
- return ret;
- ret = -E_NORESULT;
- query = make_message("select def from streams where name='%s'", name);
- free(name);
- result = get_result(query);
- free(query);
- if (!result)
- goto out;
- ret = -E_NOROW;
- row = mysql_fetch_row(result);
- if (!row || !row[0])
- goto out;
- /* no '\n' needed */
- ret = send_buffer(fd, row[0]);
-out:
- if (result)
- mysql_free_result(result);
- return ret;
-}
-
-/* change stream / change stream and play */
-static int com_cs(int fd, int argc, char *argv[])
-{
- int ret, stream_change;
- char *query;
- char *old_stream = get_current_stream();
- int csp = !strcmp(argv[0], "csp");
-
- if (!argc) {
- ret = -E_MYSQL_SYNTAX;
- if (csp)
- goto out;
- ret = send_va_buffer(fd, "%s\n", old_stream);
- goto out;
- }
- ret = -E_GET_QUERY;
- query = get_query(argv[1], NULL, 0); /* test if stream is valid */
- if (!query)
- goto out;
- free(query);
- /* stream is ok */
- stream_change = strcmp(argv[1], old_stream);
- if (stream_change) {
- ret = change_stream(argv[1]);
- if (ret < 0)
- goto out;
- refresh_mmd_dbinfo();
- }
- if (csp) {
- mmd_lock();
- mmd->new_afs_status_flags |= AFS_PLAYING;
- if (stream_change)
- mmd->new_afs_status_flags |= AFS_NEXT;
- mmd_unlock();
- }
- ret = 1;
-out:
- free(old_stream);
- return ret;
-}
-
-/*
- * sl/skip
- */
-static int com_sl(int fd, int argc, char *argv[])
-{
- void *result = NULL;
- MYSQL_ROW row;
- int ret, i, skip = !strcmp(argv[0], "skip");
- char *query, *stream, *tmp;
- unsigned int num_rows, num;
-
- if (argc < 1)
- return -E_MYSQL_SYNTAX;
- num = atoi(argv[1]);
- if (!num)
- return -E_MYSQL_SYNTAX;
- stream = (argc == 1)? get_current_stream() : para_strdup(argv[2]);
- tmp = get_query(stream, NULL, 0);
- query = make_message("%s limit %d", tmp, num);
- free(tmp);
- ret = -E_GET_QUERY;
- free(stream);
- if (!query)
- goto out;
- ret = -E_NORESULT;
- result = get_result(query);
- free(query);
- if (!result)
- goto out;
- ret = -E_EMPTY_RESULT;
- num_rows = mysql_num_rows(result);
- if (!num_rows)
- goto out;
- for (i = 0; i < num_rows && i < num; i++) {
- row = mysql_fetch_row(result);
- if (skip) {
- send_va_buffer(fd, "Skipping %s\n", row[0]);
- update_audio_file(row[0]);
- } else
- send_va_buffer(fd, "%s\n", row[0]? row[0]: "BUG");
- }
- ret = 1;
-out:
- if (result)
- mysql_free_result(result);
- return ret;
-}
-
-/*
- * update attributes of name
- */
-static int update_atts(int fd, char *name, char *atts)
-{
- int ret;
- char *ebn, *q, *old, *new = NULL;
-
- if (!mysql_ptr)
- return -E_NOTCONN;
- ebn = escaped_basename(name);
- if (!ebn)
- return -E_ESCAPE;
- q = make_message("update data set %s where name = '%s'", atts, ebn);
- old = get_atts(ebn, 0);
- send_va_buffer(fd, "old: %s\n", old);
- free(old);
- ret = real_query(q);
- free(q);
- if (ret < 0)
- goto out;
- new = get_atts(ebn, 0);
- ret = send_va_buffer(fd, "new: %s\n", new);
- free(new);
-out:
- free(ebn);
- return ret;
-}
-
-/*
- * set attributes
- */
-static int com_sa(int fd, int argc, char *argv[])
-{
- int i, ret;
- char *atts = NULL, *name;
-
- if (argc < 1)
- return -E_MYSQL_SYNTAX;
- for (i = 1; i <= argc; i++) {
- int unset = 0;
- char *tmp, *p =argv[i];
- int len = strlen(p);
-
- if (!len)
- continue;
- switch (p[len - 1]) {
- case '+':
- unset = 0;
- break;
- case '-':
- unset = 1;
- break;
- default:
- goto no_more_atts;
- }
- p[len - 1] = '\0';
- tmp = make_message("%s%s='%s'", atts? "," : "", p,
- unset? "0" : "1");
- atts = para_strcat(atts, tmp);
- free(tmp);
- }
-no_more_atts:
- if (!atts)
- return -E_NOATTS;
- if (i > argc) { /* no name given, use current af */
- ret = -E_GET_AUDIO_FILE;
- if (!(name = get_current_audio_file()))
- goto out;
- ret = update_atts(fd, name, atts);
- free(name);
- } else {
- ret = 1;
- for (; argv[i] && ret >= 0; i++)
- ret = update_atts(fd, argv[i], atts);
- }
- refresh_mmd_dbinfo();
-out:
- return ret;
-}
-
-/*
- * copy attributes
- */
-static int com_cam(int fd, int argc, char *argv[])
-{
- char *name = NULL, *meta = NULL, *atts = NULL;
- int i, ret;
-
- if (argc < 2)
- return -E_MYSQL_SYNTAX;
- if (!(name = escaped_basename(argv[1])))
- return -E_ESCAPE;
- ret = -E_NOATTS;
- if (!(atts = get_atts(name, 1)))
- goto out;
- ret = -E_META;
- if (!(meta = get_meta(name, 0)))
- goto out;
- for (i = 2; i <= argc; i++) {
- char *ebn, *q;
- ret = -E_ESCAPE;
- if (!(ebn = escaped_basename(argv[i])))
- goto out;
- ret = send_va_buffer(fd, "updating %s\n", ebn);
- if (ret < 0) {
- free(ebn);
- goto out;
- }
- q = make_message("update data set %s where name = '%s'",
- meta, ebn);
- if ((ret = update_atts(fd, ebn, atts)) >= 0)
- ret = real_query(q);
- free(ebn);
- free(q);
- if (ret < 0)
- goto out;
- }
- ret = 1;
-out:
- if (name)
- free(name);
- if (meta)
- free(meta);
- if (atts)
- free(atts);
- return ret;
-}
-
-/*
- * verify / clean
- */
-static int com_vrfy(int fd, int argc, __unused char *argv[])
-{
- char *query;
- int ret, vrfy_mode = strcmp(argv[0], "clean");
- void *result = NULL;
- unsigned int num_rows;
- MYSQL_ROW row;
- char *escaped_name;
-
- if (argc)
- return -E_MYSQL_SYNTAX;
- ret = -E_NORESULT;
- result = get_result("select data.name from data left join dir on "
- "dir.name = data.name where dir.name is NULL");
- if (!result)
- goto out;
- num_rows = mysql_num_rows(result);
- if (!num_rows) {
- ret = send_buffer(fd, "No invalid entries\n");
- goto out;
- }
- if (vrfy_mode) {
- send_va_buffer(fd, "found %i invalid entr%s\n", num_rows,
- num_rows == 1? "y" : "ies");
- ret = print_results(fd, result, 0, 0, num_rows - 1, 0);
- goto out;
- }
- while ((row = mysql_fetch_row(result))) {
- ret = -E_NOROW;
- if (!row[0])
- goto out;
- ret = -E_ESCAPE;
- escaped_name = escape_str(row[0]);
- if (!escaped_name)
- goto out;
- send_va_buffer(fd, "deleting %s\n", escaped_name);
- query = make_message("delete from data where name = '%s'",
- escaped_name);
- ret = real_query(query);
- free(query);
- if (ret < 0)
- goto out;
- }
-
-out:
- if (result)
- mysql_free_result(result);
- return ret;
-}
-
-static FILE *out_file;
-
-static int mysql_write_tmp_file(const char *dir, const char *name)
-{
- int ret = -E_TMPFILE;
- char *msg = make_message("%s\t%s\n", dir, name);
-
- if (fputs(msg, out_file) != EOF)
- ret = 1;
- free(msg);
- return ret;
-}
-
-/*
- * update database
- */
-static int com_upd(int fd, int argc, __unused char *argv[])
-{
- char *tempname = NULL, *query = NULL;
- int ret, out_fd = -1, num = 0;
- void *result = NULL;
- unsigned int num_rows;
- MYSQL_ROW row;
-
- if (argc)
- return -E_MYSQL_SYNTAX;
- out_file = NULL;
- tempname = para_strdup("/tmp/mysql.tmp.XXXXXX");
- ret = para_mkstemp(tempname, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
- if (ret < 0)
- goto out;
- out_fd = ret;
- out_file = fdopen(out_fd, "w");
- if (!out_file) {
- close(out_fd);
- goto out;
- }
- if (find_audio_files(conf.mysql_audio_file_dir_arg, mysql_write_tmp_file) < 0)
- goto out;
- num = ftell(out_file);
- /*
- * we have to make sure the file hit the disk before we call
- * real_query
- */
- fclose(out_file);
- out_file = NULL;
- PARA_DEBUG_LOG("wrote tempfile %s (%d bytes)\n", tempname, num);
- if (!num)
- goto out;
- if ((ret = real_query("delete from dir")) < 0)
- goto out;
- query = make_message("load data infile '%s' into table dir "
- "fields terminated by '\t' lines terminated by '\n' "
- "(dir, name)", tempname);
- ret = real_query(query);
- free(query);
- if (ret < 0)
- goto out;
- result = get_result("select dir.name from dir left join data on "
- "data.name = dir.name where data.name is NULL");
- ret = -E_NORESULT;
- if (!result)
- goto out;
- num_rows = mysql_num_rows(result);
- if (!num_rows) {
- ret = send_buffer(fd, "no new entries\n");
- goto out;
- }
- while ((row = mysql_fetch_row(result))) {
- ret = -E_NOROW;
- if (!row[0])
- goto out;
- send_va_buffer(fd, "new entry: %s\n", row[0]);
- query = make_message("insert into data (name, pic_id) values "
- "('%s','%s')", row[0], "1");
- ret = real_query(query);
- free(query);
- if (ret < 0)
- goto out;
- }
- ret = 1;
-out:
- if (out_fd >= 0)
- unlink(tempname);
- free(tempname);
- if (out_file)
- fclose(out_file);
- if (result)
- mysql_free_result(result);
- return ret;
-}
-
-static char **server_get_audio_file_list(unsigned int num)
-{
- char **list = para_malloc((num + 1) * sizeof(char *));
- char *tmp, *query, *stream = get_current_stream();
- void *result = NULL;
- unsigned int num_rows;
- int i = 0;
- MYSQL_ROW row;
-
- tmp = get_query(stream, NULL, 1);
- free(stream);
- query = make_message("%s limit %d", tmp, num);
- free(tmp);
- if (!query)
- goto err_out;
- result = get_result(query);
- if (!result)
- goto err_out;
- num_rows = mysql_num_rows(result);
- if (!num_rows)
- goto err_out;
- for (i = 0; i < num_rows && i < num; i++) {
- row = mysql_fetch_row(result);
- if (!row || !row[0])
- goto err_out;
- list[i] = para_strdup(row[0]);
- }
- list[i] = NULL;
- goto success;
-err_out:
- while (i > 0) {
- i--;
- free(list[i]);
- }
- free(list);
- list = NULL;
-success:
- if (query)
- free(query);
- if (result)
- mysql_free_result(result);
- return list;
-}
-
-/*
- * connect to mysql server, return mysql pointer on success, -E_NOTCONN
- * on errors. Called from parent on startup and also from com_cdb().
- */
-static int init_mysql_server(void)
-{
- char *u = conf.mysql_user_arg? conf.mysql_user_arg : para_logname();
-
- mysql_ptr = mysql_init(NULL);
- if (!mysql_ptr) {
- PARA_CRIT_LOG("%s", "mysql init error\n");
- return -E_NOTCONN;
- }
- PARA_DEBUG_LOG("connecting: %s@%s:%d\n", u, conf.mysql_host_arg,
- conf.mysql_port_arg);
- if (!conf.mysql_user_arg)
- free(u);
- /*
- * If host is NULL a connection to the local host is assumed,
- * If user is NULL, the current user is assumed
- */
- if (!(mysql_ptr = mysql_real_connect(mysql_ptr,
- conf.mysql_host_arg,
- conf.mysql_user_arg,
- conf.mysql_passwd_arg,
- conf.mysql_database_arg,
- conf.mysql_port_arg, NULL, 0))) {
- PARA_CRIT_LOG("%s", "connect error\n");
- return -E_NOTCONN;
- }
- PARA_INFO_LOG("%s", "success\n");
- return 1;
-}
-
-/* mmd lock must be held */
-static void write_msg2mmd(int success)
-{
- sprintf(mmd->dbinfo, "dbinfo1:%s\ndbinfo2:mysql-%s\ndbinfo3:\n",
- success < 0? PARA_STRERROR(-success) :
- "successfully connected to mysql server",
- success < 0? "" : mysql_get_server_info(mysql_ptr));
-}
-
-/* create database */
-static int com_cdb(int fd, int argc, char *argv[])
-{
- char *query, *name;
- int ret;
-
- if (argc < 1)
- name = "paraslash";
- else {
- ret = -E_NAMETOOLONG;
- name = argv[1];
- if (strlen(name) > MAXLINE)
- goto out;
- }
- if (mysql_ptr) {
- PARA_INFO_LOG("%s", "closing database\n");
- mysql_close(mysql_ptr);
- }
- /* dont use any database */
- conf.mysql_database_arg = NULL; /* leak? */
- ret = -E_MYSQL_INIT;
- if (init_mysql_server() < 0 || !mysql_ptr)
- goto out;
- query = make_message("create database %s", name);
- ret = real_query(query);
- free(query);
- if (ret < 0)
- goto out;
- /* reconnect with database just created */
- mysql_close(mysql_ptr);
- conf.mysql_database_arg = para_strdup(name);
- ret = -E_MYSQL_INIT;
- if (init_mysql_server() < 0 || !mysql_ptr)
- goto out;
- mmd_lock();
- write_msg2mmd(1);
- mmd_unlock();
- ret = -E_QFAILED;
- if (real_query("create table data (name varchar(255) binary not null "
- "primary key, "
- "lastplayed datetime not null default "
- "'1970-01-01', "
- "numplayed int not null default 0, "
- "pic_id bigint unsigned not null default 1)") < 0)
- goto out;
- if (real_query("create table dir (name varchar(255) binary not null "
- "primary key, dir varchar(255) default null)") < 0)
- goto out;
- if (real_query("create table pics ("
- "id bigint(20) unsigned not null primary key "
- "auto_increment, "
- "name varchar(255) binary not null, "
- "pic mediumblob not null)") < 0)
- goto out;
- if (real_query("create table streams ("
- "name varchar(255) binary not null primary key, "
- "def blob not null)") < 0)
- goto out;
- if (real_query("insert into streams (name, def) values "
- "('current_stream', '(none)')") < 0)
- goto out;
- ret = send_va_buffer(fd, "successfully created database %s\n", name);
-out:
- return ret;
-}
-
-static void shutdown_connection(void)
-{
- if (mysql_ptr) {
- PARA_NOTICE_LOG("%s", "shutting down mysql connection\n");
- mysql_close(mysql_ptr);
- mysql_ptr = NULL;
- }
-}
-
-/**
- * the init function of the mysql-based database tool
- *
- * Check the command line options and initialize all function pointers of \a db.
- * Connect to the mysql server and initialize the dbinfo string.
- *
- * \sa struct dbtool, misc_meta_data::dbinfo, random_dbtool.c
- */
-int mysql_dbtool_init(struct dbtool *db)
-{
- int ret;
-
- if (!conf.mysql_passwd_given)
- return -E_NO_MYSQL_PASSWD;
- if (!conf.mysql_audio_file_dir_given)
- return -E_NO_AF_DIR;
- db->name = "mysql";
- db->cmd_list = cmds;
- db->get_audio_file_list = server_get_audio_file_list;
- db->update_audio_file = update_audio_file_server_handler;
- db->shutdown = shutdown_connection;
- ret = init_mysql_server();
- if (ret < 0)
- PARA_WARNING_LOG("%s\n", PARA_STRERROR(-ret));
- write_msg2mmd(ret);
- return 1; /* return success even if connect failed to give the
- * user the chance to exec com_cdb
- */
-}
--- /dev/null
+/*
+ * Copyright (C) 1999-2006 Andre Noll <maan@systemlinux.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, USA.
+ */
+
+/** \file mysql_selector.c para_server's mysql-based audio file selector */
+
+/** \cond some internal constants */
+#define MEDIUM_BLOB_SIZE 16777220 /* (2**24 + 4) */
+#define BLOB_SIZE 65539 /* (2**16 + 3) */
+/** \endcond */
+#include "server.cmdline.h"
+#include "server.h"
+#include "afs.h"
+#include "db.h"
+#include <mysql/mysql.h>
+#include <mysql/mysql_version.h>
+#include "error.h"
+#include "net.h"
+#include "string.h"
+
+extern struct gengetopt_args_info conf;
+/** pointer to the shared memory area */
+extern struct misc_meta_data *mmd;
+
+static void *mysql_ptr = NULL;
+
+
+static int com_cam(int, int, char **);
+static int com_cdb(int, int, char **);
+static int com_cs(int, int, char **);
+static int com_da(int, int, char **);
+static int com_hist(int, int, char **);
+static int com_info(int, int, char **);
+static int com_laa(int, int, char **);
+static int com_last(int, int, char **);
+static int com_ls(int, int, char **);
+static int com_mbox(int, int, char **);
+static int com_mv(int, int, char **);
+static int com_na(int, int, char **);
+static int com_pic(int, int, char **);
+static int com_picch(int, int, char **);
+static int com_picdel(int, int, char **);
+static int com_piclist(int, int, char **);
+static int com_ps(int, int, char **);
+static int com_rm_ne(int, int, char **);
+static int com_sa(int, int, char **);
+static int com_set(int, int, char **);
+static int com_sl(int, int, char **);
+static int com_stradd_picadd(int, int, char **);
+static int com_streams(int, int, char **);
+static int com_strdel(int, int, char **);
+static int com_strq(int, int, char **);
+static int com_summary(int, int, char **);
+static int com_upd(int, int, char **);
+static int com_us(int, int, char **);
+static int com_verb(int, int, char **);
+static int com_vrfy(int, int, char **);
+
+static struct server_command cmds[] = {
+{
+.name = "cam",
+.handler = com_cam,
+.perms = DB_READ|DB_WRITE,
+.description = "copy all metadata",
+.synopsis = "cam source dest1 [dest2 ...]",
+.help =
+
+"Copy attributes and other meta data from source file to destination\n"
+"file(s). Useful for files that have been renamed.\n"
+
+},
+{
+.name = "cdb",
+.handler = com_cdb,
+.perms = DB_READ|DB_WRITE,
+.description = "create database",
+.synopsis = "cdb [name]",
+.help =
+
+"\tCreate database name containing the initial columns for basic\n"
+"\tinteroperation with server. This command has to be used only once\n"
+"\twhen you use the mysql audio file selector for the very first time.\n"
+"\n"
+"\tThe optional name defaults to 'paraslash' if not given.\n"
+
+},
+{
+.name = "clean",
+.handler = com_vrfy,
+.perms = DB_READ | DB_WRITE,
+.description = "nuke invalid entries in database",
+.synopsis = "clean",
+.help =
+
+"If the vrfy command shows you any invalid entries in your database,\n"
+"you can get rid of them with clean. Always run 'upd' and 'vrfy'\n"
+"before running this command. Use with caution!\n"
+
+},
+{
+.name = "cs",
+.handler = com_cs,
+.perms = AFS_WRITE | DB_READ | DB_WRITE,
+.description = "change stream",
+.synopsis = "cs [s]",
+.help =
+
+"Selects stream s or prints current stream when s was not given.\n"
+
+},
+{
+.name = "csp",
+.handler = com_cs,
+.perms = AFS_WRITE | DB_READ,
+.description = "change stream and play",
+.synopsis = "csp s",
+.help =
+
+"Select stream s and start playing. If this results in a stream-change,\n"
+"skip rest of current audio file.\n"
+
+},
+{
+.name = "da",
+.handler = com_da,
+.perms = DB_READ | DB_WRITE,
+.description = "drop attribute from database",
+.synopsis = "da att",
+.help =
+
+"Use with caution. All info on attribute att will be lost.\n"
+
+},
+{
+.name = "hist",
+.handler = com_hist,
+.perms = DB_READ,
+.description = "print history",
+.synopsis = "hist",
+.help =
+
+"Print list of all audio files together with number of days since each\n"
+"file was last played.\n"
+
+},
+{
+.name = "info",
+.handler = com_info,
+.perms = DB_READ,
+.description = "print database info",
+.synopsis = "info [af]",
+.help =
+
+"print database informations for audio file af. Current audio file is\n"
+"used if af is not given.\n"
+
+},
+{
+.name = "la",
+.handler = com_info,
+.perms = DB_READ,
+.description = "list attributes",
+.synopsis = "la [af]",
+.help =
+
+"List attributes of audio file af or of current audio file when invoked\n"
+"without arguments.\n"
+
+},
+{
+.name = "laa",
+.handler = com_laa,
+.perms = DB_READ,
+.description = "list available attributes",
+.synopsis = "laa",
+.help =
+
+"What should I say more?\n"
+
+},
+{
+.name = "last",
+.handler = com_last,
+.perms = DB_READ,
+.description = "print list of audio files, ordered by lastplayed time",
+.synopsis = "last [n]",
+.help =
+
+"The optional number n defaults to 10 if not specified.\n"
+
+},
+{
+.name = "ls",
+.handler = com_ls,
+.perms = DB_READ,
+.description = "list all audio files that match a LIKE pattern",
+.synopsis = "ls [pattern]",
+.help =
+
+"\tIf pattern was not given, print list of all audio files known\n"
+"\tto the mysql selector. See the documentation of mysql\n"
+"\tfor the definition of LIKE patterns.\n"
+
+},
+{
+.name = "mbox",
+.handler = com_mbox,
+.perms = DB_READ,
+.description = "dump audio file list in mbox format",
+.synopsis = "mbox [p]",
+.help =
+
+"\tDump list of audio files in mbox format (email) to stdout. If\n"
+"\tthe optional pattern p is given, only those audio files,\n"
+"\twhose basename match p are going to be included. Otherwise,\n"
+"\tall files are selected.\n"
+"\n"
+"EXAMPLE\n"
+"\tThe mbox command can be used together with your favorite\n"
+"\tmailer (this example uses mutt) for browsing the audio file\n"
+"\tcollection:\n"
+"\n"
+"\t\tpara_client mbox > ~/para_mbox\n"
+"\n"
+"\t\tmutt -F ~/.muttrc.para -f ~/para_mbox\n"
+"\n"
+"\tFor playlists, you can use mutt's powerful pattern matching\n"
+"\tlanguage to select files. If you like to tag all files\n"
+"\tcontaining the pattern 'foo', type 'T', then '~s foo'.\n"
+"\n"
+"\tWhen ready with the list, type ';|' (i.e., hit the semicolon\n"
+"\tkey to apply the next mutt command to all tagged messages,\n"
+"\tthen the pipe key) to pipe the selected \"mails\" to a\n"
+"\tsuitable script which adds a paraslash stream where exactly\n"
+"\tthese files are admissable or does whatever thou wilt.\n"
+
+},
+{
+.name = "mv",
+.handler = com_mv,
+.perms = DB_READ | DB_WRITE,
+.description = "rename entry in database",
+.synopsis = "mv oldname newname",
+.help =
+
+"Rename oldname to newname. This updates the data table to reflect the\n"
+"new name. All internal data (numplayed, lastplayed, picid,..) is kept.\n"
+"If newname is a full path, the dir table is updated as well.\n"
+
+},
+{
+.name = "na",
+.handler = com_na,
+.perms = DB_READ | DB_WRITE,
+.description = "add new attribute to database",
+.synopsis = "na att",
+.help =
+
+"This adds a column named att to your mysql database. att should only\n"
+"contain letters and numbers, in paricular, '+' and '-' are not allowed.\n"
+
+},
+{
+.name = "ne",
+.handler = com_rm_ne,
+.perms = DB_READ | DB_WRITE,
+.description = "add new database entries",
+.synopsis = "ne file1 [file2 [...]]",
+.help =
+
+"Add the given filename(s) to the database, where file1,... must\n"
+"be full path names. This command might be much faster than 'upd'\n"
+"if the number of given files is small.\n"
+
+},
+{
+.name = "ns",
+.handler = com_ps,
+.perms = AFS_WRITE | DB_READ | DB_WRITE,
+.description = "change to next stream",
+.synopsis = "ns",
+.help =
+
+"Cycle forwards through stream list.\n"
+
+},
+{
+.name = "pic",
+.handler = com_pic,
+.perms = DB_READ,
+.description = "get picture by name or by identifier",
+.synopsis = "pic [name]",
+.help =
+
+"\tDump jpg image that is associated to given audio file (current\n"
+"\taudio file if not specified) to stdout. If name starts with\n"
+"\t'#' it is interpreted as an identifier instead and the picture\n"
+"\thaving that identifier is dumped to stdout.\n"
+"\n"
+"EXAMPLE\n"
+"\n"
+"\tpara_client pic '#123' > pic123.jpg\n"
+
+},
+{
+.name = "picadd",
+.handler = com_stradd_picadd,
+.perms = DB_READ | DB_WRITE,
+.description = "add picture to database",
+.synopsis = "picadd [picname]",
+.help =
+
+"\tRead jpeg file from stdin and store it as picname in database.\n"
+"\n"
+"EXAMPLE\n"
+"\n"
+"\tpara_client picadd foo.jpg < foo.jpg\n"
+
+},
+{
+.name = "picass",
+.handler = com_set,
+.perms = DB_READ | DB_WRITE,
+.description = "associate a picture to file(s)",
+.synopsis = "picass pic_id file1 [file2...]",
+.help =
+
+"Associate the picture given by pic_id to all given files.\n"
+
+},
+{
+.name = "picch",
+.handler = com_picch,
+.perms = DB_READ | DB_WRITE,
+.description = "change name of picture",
+.synopsis = "picch id new_name",
+.help =
+
+"Asign new_name to picture with identifier id.\n"
+
+},
+{
+.name = "picdel",
+.handler = com_picdel,
+.perms = DB_READ | DB_WRITE,
+.description = "delete picture from database",
+.synopsis = "picdel id1 [id2...]",
+.help =
+
+"Delete each given picture from database.\n"
+
+},
+{
+.name = "piclist",
+.handler = com_piclist,
+.perms = DB_READ,
+.description = "print list of pictures",
+.synopsis = "piclist",
+.help =
+
+"Print id, name and length of each picture contained in the database.\n"
+
+},
+{
+.name = "ps",
+.handler = com_ps,
+.perms = AFS_WRITE | DB_READ | DB_WRITE,
+.description = "change to previous stream",
+.synopsis = "ps",
+.help =
+
+"Cycle backwards through stream list.\n"
+
+},
+{
+.name = "rm",
+.handler = com_rm_ne,
+.perms = DB_READ | DB_WRITE,
+.description = "remove entries from database",
+.synopsis = "rm name1 [name2 [...]]",
+.help =
+
+"Remove name1, name2, ... from the data table. Use with caution\n"
+
+},
+{
+.name = "sa",
+.handler = com_sa,
+.perms = DB_READ | DB_WRITE,
+.description = "set/unset attributes",
+.synopsis = "sa at1<'+' | '-'> [at2<'+' | '-'> ] [af1 ...]",
+//.synopsis = "foo",
+.help =
+
+"Set ('+') or unset ('-') attribute at1, at2 etc. for given list of\n"
+"audio files. If no audio files were given the current audio file is\n"
+"used. Example:\n"
+"\n"
+"sa rock+ punk+ classic- LZ__Waldsterben.mp3\n"
+"\n"
+"sets the 'rock' and the 'punk' attribute but unsets the 'classic'\n"
+"attribute.\n"
+
+},
+{
+.name = "skip",
+.handler = com_sl,
+.perms = DB_READ | DB_WRITE,
+.description = "skip subsequent audio files(s)",
+.synopsis = "skip n [s]",
+.help =
+
+"Skip the next n audio files of stream s. This is equivalent to the\n"
+"command 'sl n s', followed by 'us name' for each name the output of sl.\n"
+
+},
+{
+.name = "sl",
+.handler = com_sl,
+.perms = DB_READ,
+.description = "print score list",
+.synopsis = "sl n [s]",
+.help =
+
+"Print sorted list of maximal n lines. Each line is an admissible entry\n"
+"with respect to stream s. The list is sorted by score-value which is\n"
+"given by the definition of s. If s is not given, the current stream\n"
+"is used. Example:\n"
+"\n"
+" sl 1\n"
+"\n"
+"shows you the audio file the server would select right now.\n"
+
+},
+{
+.name = "snp",
+.handler = com_set,
+.perms = DB_READ | DB_WRITE,
+.description = "set numplayed",
+.synopsis = "snp number af1 [af2 ...]",
+.help =
+
+"Update the numplayed field in the data table for all given audio files.\n"
+
+},
+{
+.name = "stradd",
+.handler = com_stradd_picadd,
+.perms = DB_READ | DB_WRITE,
+.description = "add stream",
+.synopsis = "stradd s",
+.help =
+
+"Add stream s to the list of available streams. The stream definition\n"
+"for s is read from stdin and is then sent to para_server. Example:\n"
+"\n"
+" echo 'deny: NAME_LIKE(%Madonna%)' | para_client stradd no_madonna\n"
+"\n"
+"adds the new stream 'no_madonna' to the list of available streams. A given\n"
+"audio file is admissible for this stream iff its basename does not contain the\n"
+"string 'Madonna'.\n"
+
+
+},
+{
+.name = "strdel",
+.handler = com_strdel,
+.perms = DB_READ | DB_WRITE,
+.description = "delete stream",
+.synopsis = "strdel s",
+.help =
+
+"Remove stream s from database.\n"
+
+},
+{
+.name = "streams",
+.handler = com_streams,
+.perms = DB_READ,
+.description = "list streams",
+.synopsis = "streams",
+.help =
+
+"Print list of available streams. Use 'cs' to switch to any of these.\n"
+
+},
+{
+.name = "strq",
+.handler = com_strq,
+.perms = DB_READ,
+.description = "query stream definition",
+.synopsis = "strq [s]",
+.help =
+
+"Print definition of stream s to stdout. Use current stream if s was\n"
+"not given.\n"
+
+},
+{
+.name = "summary",
+.handler = com_summary,
+.perms = DB_READ,
+.description = "list attributes",
+.synopsis = "summary",
+.help =
+
+"\tPrint a list of attributes together with number of audio\n"
+"\tfiles having that attribute set.\n"
+
+},
+{
+.name = "upd",
+.handler = com_upd,
+.perms = DB_READ | DB_WRITE,
+.description = "update database",
+.synopsis = "upd",
+.help =
+
+"This command uses the --audio_file_dir option of para_server to locate\n"
+"your audio files. New files are then added to the mysql database. Use\n"
+"this command if you got new files or if you have moved some files\n"
+"around.\n"
+
+},
+{
+.name = "us",
+.handler = com_us,
+.perms = DB_READ | DB_WRITE,
+.description = "update lastplayed time",
+.synopsis = "us name",
+.help =
+
+"Update lastplayed time without actually playing the thing.\n"
+
+},
+{
+.name = "verb",
+.handler = com_verb,
+.perms = DB_READ | DB_WRITE,
+.description = "send verbatim sql query",
+.synopsis = "verb cmd",
+.help =
+
+"Send cmd to mysql server. For expert/debugging only. Note that cmd\n"
+"usually must be escaped. Use only if you know what you are doing!\n"
+
+},
+{
+.name = "vrfy",
+.handler = com_vrfy,
+.perms = DB_READ,
+.description = "list invalid entries in database",
+.synopsis = "vrfy",
+.help =
+
+"Show what clean would delete. Run 'upd' before this command to make\n"
+"sure your database is up to date.\n"
+
+},
+{
+.name = NULL,
+}
+};
+
+static struct para_macro macro_list[] = {
+ { .name = "IS_N_SET",
+ .replacement = "(data.%s != '1')"
+ }, {
+ .name = "IS_SET",
+ .replacement = "(data.%s = '1')"
+ }, {
+ .name = "PICID",
+ .replacement = "%sdata.Pic_Id"
+ }, {
+ .name = "NAME_LIKE",
+ .replacement = "(data.name like '%s')"
+ }, {
+ .name = "LASTPLAYED",
+ .replacement = "%sFLOOR((UNIX_TIMESTAMP(now())"
+ "-UNIX_TIMESTAMP(data.Lastplayed))/60)"
+ }, {
+ .name = "NUMPLAYED",
+ .replacement = "%sdata.Numplayed"
+ }, {
+ .name = NULL,
+ }
+};
+
+static int real_query(char *query)
+{
+ if (!mysql_ptr)
+ return -E_NOTCONN;
+ PARA_DEBUG_LOG("%s\n", query);
+ if (mysql_real_query(mysql_ptr, query, strlen(query))) {
+ PARA_ERROR_LOG("real_query error (%s)\n",
+ mysql_error(mysql_ptr));
+ return -E_QFAILED;
+ }
+ return 1;
+}
+
+/*
+ * Use open connection given by mysql_ptr to query server. Returns a
+ * result pointer on succes and NULL on errors
+ */
+static struct MYSQL_RES *get_result(char *query)
+{
+ void *result;
+
+ if (real_query(query) < 0)
+ return NULL;
+ result = mysql_store_result(mysql_ptr);
+ if (!result)
+ PARA_ERROR_LOG("%s", "store_result error\n");
+ return result;
+}
+/*
+ * write input from fd to dynamically allocated char array,
+ * but maximal max_size byte. Return size.
+ */
+static int fd2buf(int fd, char **buf_ptr, size_t max_size)
+{
+ const size_t chunk_size = 1024;
+ size_t size = 2048;
+ char *buf = para_malloc(size * sizeof(char)), *p = buf;
+ int ret;
+
+ while ((ret = recv_bin_buffer(fd, p, chunk_size)) > 0) {
+ p += ret;
+ if ((p - buf) + chunk_size >= size) {
+ char *tmp;
+
+ size *= 2;
+ if (size > max_size) {
+ ret = -E_TOOBIG;
+ goto out;
+ }
+ tmp = para_realloc(buf, size);
+ p = (p - buf) + tmp;
+ buf = tmp;
+ }
+ }
+ if (ret < 0)
+ goto out;
+ *buf_ptr = buf;
+ ret = p - buf;
+out:
+ if (ret < 0 && buf)
+ free(buf);
+ return ret;
+}
+
+static char *escape_blob(char* old, int size)
+{
+ char *new;
+
+ if (!mysql_ptr || size < 0)
+ return NULL;
+ new = para_malloc(2 * size * sizeof(char) + 1);
+ mysql_real_escape_string(mysql_ptr, new, old, size);
+ return new;
+}
+
+static char *escape_str(char* old)
+{
+ return escape_blob(old, strlen(old));
+}
+
+static char *escaped_basename(const char *name)
+{
+ char *esc, *bn = para_basename(name);
+
+ if (!bn)
+ return NULL;
+ esc = escape_str(bn);
+ free(bn);
+ return esc;
+}
+
+/*
+ * new attribute
+ */
+static int com_na(__unused int fd, int argc, char *argv[])
+{
+ char *q;
+ int ret;
+
+ if (argc < 1)
+ return -E_MYSQL_SYNTAX;
+ q = make_message("alter table data add %s char(1) "
+ "not null default 0", argv[1]);
+ ret = real_query(q);
+ free(q);
+ return ret;
+}
+
+/*
+ * delete attribute
+ */
+static int com_da(__unused int fd, int argc, char *argv[])
+{
+ char *q;
+ int ret;
+
+ if (argc < 1)
+ return -E_MYSQL_SYNTAX;
+ q = make_message("alter table data drop %s", argv[1]);
+ ret = real_query(q);
+ free(q);
+ return ret;
+}
+
+/* stradd/pic_add */
+static int com_stradd_picadd(int fd, int argc, char *argv[])
+{
+ char *blob = NULL, *esc_blob = NULL, *q;
+ const char *fmt, *del_fmt;
+ int ret, stradd = strcmp(argv[0], "picadd");
+ size_t size;
+
+ if (argc < 1)
+ return -E_MYSQL_SYNTAX;
+ if (strlen(argv[1]) >= MAXLINE - 1)
+ return -E_NAMETOOLONG;
+ if (!mysql_ptr)
+ return -E_NOTCONN;
+ if (stradd) {
+ size = BLOB_SIZE;
+ fmt = "insert into streams (name, def) values ('%s','%s')";
+ del_fmt="delete from streams where name='%s'";
+ } else {
+ size = MEDIUM_BLOB_SIZE;
+ fmt = "insert into pics (name, pic) values ('%s','%s')";
+ del_fmt="delete from pics where pic='%s'";
+ }
+ q = make_message(del_fmt, argv[1]);
+ ret = real_query(q);
+ free(q);
+ if (ret < 0)
+ return ret;
+ if ((ret = send_buffer(fd, AWAITING_DATA_MSG) < 0))
+ return ret;
+ if ((ret = fd2buf(fd, &blob, size)) < 0)
+ return ret;
+ PARA_DEBUG_LOG("length: %i\n", ret);
+ size = ret;
+ if (stradd)
+ blob[size] = '\0';
+ esc_blob = escape_blob(blob, ret);
+ free(blob);
+ if (!esc_blob)
+ return -E_TOOBIG;
+ q = make_message(fmt, argv[1], esc_blob);
+ free(esc_blob);
+ ret = real_query(q);
+ free(q);
+ return ret;
+}
+
+/*
+ * print results to fd
+ */
+static int print_results(int fd, void *result,
+ unsigned int top, unsigned int left,
+ unsigned int bottom, unsigned int right)
+{
+ unsigned int i,j;
+ int ret;
+ MYSQL_ROW row;
+
+ for (i = top; i <= bottom; i++) {
+ row = mysql_fetch_row(result);
+ if (!row || !row[0])
+ return -E_NOROW;
+ for (j = left; j <= right; j++) {
+ ret = send_va_buffer(fd, j == left? "%s" : "\t%s",
+ row[j]? row[j] : "NULL");
+ if (ret < 0)
+ return ret;
+ }
+ ret = send_buffer(fd, "\n");
+ if (ret < 0)
+ return ret;
+ }
+ return 0;
+}
+
+/*
+ * verbatim
+ */
+static int com_verb(int fd, int argc, char *argv[])
+{
+ void *result = NULL;
+ int ret;
+ unsigned int num_rows, num_fields;
+
+ if (argc < 1)
+ return -E_MYSQL_SYNTAX;
+ result = get_result(argv[1]);
+ if (!result)
+ /* return success, because it's ok to have no results */
+ return 1;
+ num_fields = mysql_field_count(mysql_ptr);
+ num_rows = mysql_num_rows(result);
+ ret = 1;
+ if (num_fields && num_rows)
+ ret = print_results(fd, result, 0, 0, num_rows - 1,
+ num_fields - 1);
+ mysql_free_result(result);
+ return ret;
+}
+
+/* returns NULL on errors or if there are no atts defined yet */
+static void *get_all_attributes(void)
+{
+ void *result = get_result("desc data");
+ unsigned int num_rows;
+
+ if (!result)
+ return NULL;
+ num_rows = mysql_num_rows(result);
+ if (num_rows < 5) {
+ mysql_free_result(result);
+ return NULL;
+ }
+ mysql_data_seek(result, 4); /* skip Lastplayed, Numplayed... */
+ return result;
+}
+
+/*
+ * list all attributes
+ */
+static int com_laa(int fd, int argc, __unused char *argv[])
+{
+ void *result;
+ int ret;
+
+ if (argc)
+ return -E_MYSQL_SYNTAX;
+ result = get_all_attributes();
+ if (!result)
+ return -E_NOATTS;
+ ret = print_results(fd, result, 0, 0, mysql_num_rows(result) - 5, 0);
+ mysql_free_result(result);
+ return ret;
+}
+
+/*
+ * history
+ */
+static int com_hist(int fd, int argc, char *argv[]) {
+ int ret;
+ void *result = NULL;
+ char *q;
+ unsigned int num_rows;
+
+ q = make_message("select name, to_days(now()) - to_days(lastplayed) from "
+ "data%s%s%s order by lastplayed",
+ (argc < 1)? "" : " where ",
+ (argc < 1)? "" : argv[1],
+ (argc < 1)? "" : " = '1'");
+ result = get_result(q);
+ free(q);
+ if (!result)
+ return -E_NORESULT;
+ num_rows = mysql_num_rows(result);
+ ret = 1;
+ if (num_rows)
+ ret = print_results(fd, result, 0, 0, num_rows - 1, 1);
+ mysql_free_result(result);
+ return ret;
+}
+
+/*
+ * get last num audio files
+ */
+static int com_last(int fd, int argc, char *argv[])
+{
+ void *result = NULL;
+ char *q;
+ int num, ret;
+
+ if (argc < 1)
+ num = 10;
+ else
+ num = atoi(argv[1]);
+ if (!num)
+ return -E_MYSQL_SYNTAX;
+ q = make_message("select name from data order by lastplayed desc "
+ "limit %u", num);
+ result = get_result(q);
+ free(q);
+ if (!result)
+ return -E_NORESULT;
+ ret = print_results(fd, result, 0, 0, mysql_num_rows(result) - 1, 0);
+ mysql_free_result(result);
+ return ret;
+}
+
+static int com_mbox(int fd, int argc, char *argv[])
+{
+ void *result;
+ MYSQL_ROW row;
+ int ret;
+ unsigned int num_rows, num_fields;
+ char *query = para_strdup("select concat('From foo@localhost ', "
+ "date_format(Lastplayed, '%a %b %e %T %Y'), "
+ "'\nReceived: from\nTo: bar\n");
+
+ ret = -E_NOATTS;
+ result = get_all_attributes();
+ if (!result)
+ goto out;
+ ret = -E_NOROW;
+ while ((row = mysql_fetch_row(result))) {
+ char *tmp;
+
+ if (!row[0])
+ goto out;
+ tmp = make_message("%s X-Attribute-%s: ', %s, '\n", query,
+ row[0], row[0]);
+ free(query);
+ query = tmp;
+ }
+ query = para_strcat(query,
+ "From: a\n"
+ "Subject: "
+ "', name, '"
+ "\n\n\n"
+ "') from data"
+ );
+ if (argc >= 1) {
+ char *tmp = make_message("%s where name LIKE '%s'", query,
+ argv[1]);
+ free(query);
+ query = tmp;
+ }
+ mysql_free_result(result);
+ ret = -E_NORESULT;
+ result = get_result(query);
+ if (!result)
+ goto out;
+ ret = -E_EMPTY_RESULT;
+ num_fields = mysql_field_count(mysql_ptr);
+ num_rows = mysql_num_rows(result);
+ if (!num_fields || !num_rows)
+ goto out;
+ ret = print_results(fd, result, 0, 0, num_rows - 1, num_fields - 1);
+out:
+ free(query);
+ if (result)
+ mysql_free_result(result);
+ return ret;
+}
+
+/* get attributes by name. If verbose is not 0, get_a writes a string
+ * into atts of the form 'att1="0",att2="1"', which is used in com_cam
+ * for contructing a mysql update query.
+ * never returns NULL in *NON VERBOSE* mode
+ */
+static char *get_atts(char *name, int verbose)
+{
+ char *atts = NULL, *buf, *ebn;
+ void *result = NULL, *result2 = NULL;
+ MYSQL_ROW row, row2;
+ int i, ret;
+ unsigned int num_fields;
+
+ ret = -E_NOATTS;
+ result2 = get_all_attributes();
+ if (!result2)
+ goto out;
+ ret = -E_ESCAPE;
+ if (!(ebn = escaped_basename(name)))
+ goto out;
+ buf = make_message("select * from data where name='%s'", ebn);
+ free(ebn);
+ ret = -E_NORESULT;
+ result = get_result(buf);
+ free(buf);
+ if (!result)
+ goto out;
+ ret = -E_EMPTY_RESULT;
+ num_fields = mysql_num_fields(result);
+ if (num_fields < 5)
+ goto out;
+ mysql_data_seek(result2, 4); /* skip Lastplayed, Numplayed... */
+ row = mysql_fetch_row(result);
+ ret = -E_NOROW;
+ if (!row)
+ goto out;
+ for (i = 4; i < num_fields; i++) {
+ int is_set = row[i] && !strcmp(row[i], "1");
+ row2 = mysql_fetch_row(result2);
+ if (!row2 || !row2[0])
+ goto out;
+ if (atts && (verbose || is_set))
+ atts = para_strcat(atts, verbose? "," : " ");
+ if (is_set || verbose)
+ atts = para_strcat(atts, row2[0]);
+ if (verbose)
+ atts = para_strcat(atts, is_set? "=\"1\"" : "=\"0\"");
+ }
+ ret = 1;
+out:
+ if (result2)
+ mysql_free_result(result2);
+ if (result)
+ mysql_free_result(result);
+ if (!atts && !verbose)
+ atts = para_strdup("(none)");
+ return atts;
+}
+
+/* never returns NULL in verbose mode */
+static char *get_meta(char *name, int verbose)
+{
+ MYSQL_ROW row;
+ void *result = NULL;
+ char *ebn, *q, *ret = NULL;
+ const char *verbose_fmt =
+ "select concat('lastplayed: ', "
+ "(to_days(now()) - to_days(lastplayed)),"
+ "' day(s). numplayed: ', numplayed, "
+ "', pic: ', pic_id) "
+ "from data where name = '%s'";
+ /* is that really needed? */
+ const char *fmt = "select concat('lastplayed=\\'', lastplayed, "
+ "'\\', numplayed=\\'', numplayed, "
+ "'\\', pic_id=\\'', pic_id, '\\'') "
+ "from data where name = '%s'";
+
+ if (!(ebn = escaped_basename(name)))
+ goto out;
+ q = make_message(verbose? verbose_fmt : fmt, ebn);
+ free(ebn);
+ result = get_result(q);
+ free(q);
+ if (!result)
+ goto out;
+ row = mysql_fetch_row(result);
+ if (!row || !row[0])
+ goto out;
+ ret = para_strdup(row[0]);
+out:
+ if (result)
+ mysql_free_result(result);
+ if (!ret && verbose)
+ ret = para_strdup("(not yet played)");
+ return ret;
+}
+
+static char *get_dir(char *name)
+{
+ char *ret = NULL, *q, *ebn;
+ void *result;
+ MYSQL_ROW row;
+
+ if (!(ebn = escaped_basename(name)))
+ return NULL;
+ q = make_message("select dir from dir where name = '%s'", ebn);
+ free(ebn);
+ result = get_result(q);
+ free(q);
+ if (!result)
+ return NULL;
+ row = mysql_fetch_row(result);
+ if (row && row[0])
+ ret = para_strdup(row[0]);
+ mysql_free_result(result);
+ return ret;
+}
+
+/* never returns NULL */
+static char *get_current_stream(void)
+{
+ char *ret;
+ MYSQL_ROW row;
+ void *result = get_result("select def from streams where "
+ "name = 'current_stream'");
+
+ if (!result)
+ goto err_out;
+ row = mysql_fetch_row(result);
+ if (!row || !row[0])
+ goto err_out;
+ ret = para_strdup(row[0]);
+ mysql_free_result(result);
+ return ret;
+err_out:
+ if (result)
+ mysql_free_result(result);
+ return para_strdup("(none)");
+}
+/*
+ * Read stream definition of stream streamname and construct mysql
+ * query. Return NULL on errors. If streamname is NULL, use current
+ * stream. If that is also NULL, use query that selects everything.
+ * If filename is NULL, query will list everything, otherwise only
+ * the score of given file.
+ */
+static char *get_query(char *streamname, char *filename, int with_path)
+{
+ char *accept_opts = NULL, *deny_opts = NULL, *score = NULL;
+ char *where_clause, *order, *query;
+ char command[255] = ""; /* buffer for sscanf */
+ void *result;
+ MYSQL_ROW row;
+ char *end, *tmp;
+ char *select_clause = NULL;
+ if (!streamname)
+ tmp = get_current_stream();
+ else
+ tmp = para_strdup(streamname);
+ if (!strcmp(tmp, "(none)")) {
+ free(tmp);
+ if (filename) {
+ char *ret, *ebn = escaped_basename(filename);
+ ret = make_message("select to_days(now()) - "
+ "to_days(lastplayed) from data "
+ "where name = '%s'", ebn);
+ free(ebn);
+ return ret;
+ }
+ if (with_path)
+ return make_message(
+ "select concat(dir.dir, '/', dir.name) "
+ "from data, dir where dir.name = data.name "
+ "order by data.lastplayed"
+ );
+ return make_message(
+ "select name from data where name is not NULL "
+ "order by lastplayed"
+ );
+ }
+ free(tmp);
+ query = make_message("select def from streams where name = '%s'",
+ streamname);
+ result = get_result(query);
+ free(query);
+ query = NULL;
+ if (!result)
+ goto out;
+ row = mysql_fetch_row(result);
+ if (!row || !row[0])
+ goto out;
+ end = row[0];
+ while (*end) {
+ int n;
+ char *arg, *line = end;
+
+ if (!(end = strchr(line, '\n')))
+ break;
+ *end = '\0';
+ end++;
+ if (sscanf(line, "%200s%n", command, &n) < 1)
+ continue;
+ arg = line + n;
+ if (!strcmp(command, "accept:")) {
+ char *tmp2 = s_a_r_list(macro_list, arg);
+ if (accept_opts)
+ accept_opts = para_strcat(
+ accept_opts, " or ");
+ accept_opts = para_strcat(accept_opts, tmp2);
+ free(tmp2);
+ continue;
+ }
+ if (!strcmp(command, "deny:")) {
+ char *tmp2 = s_a_r_list(macro_list, arg);
+ if (deny_opts)
+ deny_opts = para_strcat(deny_opts, " or ");
+ deny_opts = para_strcat(deny_opts, tmp2);
+ free(tmp2);
+ continue;
+ }
+ if (!strcmp(command, "score:"))
+ score = s_a_r_list(macro_list, arg);
+ }
+ if (!score) {
+ score = s_a_r_list(macro_list, conf.mysql_default_score_arg);
+ if (!score)
+ goto out;
+ }
+ if (filename) {
+ char *ebn = escaped_basename(filename);
+ if (!ebn)
+ goto out;
+ select_clause = make_message("select %s from data ", score);
+ free(score);
+ where_clause = make_message( "where name = '%s' ", ebn);
+ free(ebn);
+ order = para_strdup("");
+ goto write_query;
+ }
+ select_clause = para_strdup(with_path?
+ "select concat(dir.dir, '/', dir.name) from data, dir "
+ "where dir.name = data.name "
+ :
+ "select name from data where name is not NULL");
+ order = make_message("order by -(%s)", score);
+ free(score);
+ if (accept_opts && deny_opts) {
+ where_clause = make_message("and ((%s) and not (%s)) ",
+ accept_opts, deny_opts);
+ goto write_query;
+ }
+ if (accept_opts && !deny_opts) {
+ where_clause = make_message("and (%s) ", accept_opts);
+ goto write_query;
+ }
+ if (!accept_opts && deny_opts) {
+ where_clause = make_message("and not (%s) ", deny_opts);
+ goto write_query;
+ }
+ where_clause = para_strdup("");
+write_query:
+ query = make_message("%s %s %s", select_clause, where_clause, order);
+ free(order);
+ free(select_clause);
+ free(where_clause);
+out:
+ if (accept_opts)
+ free(accept_opts);
+ if (deny_opts)
+ free(deny_opts);
+ if (result)
+ mysql_free_result(result);
+ return query;
+}
+
+
+
+/*
+ * This is called from server and from some commands. Name must not be NULL
+ * Never returns NULL.
+ */
+static char *get_selector_info(char *name)
+{
+ char *meta = NULL, *atts = NULL, *info, *dir = NULL, *query, *stream = NULL;
+ void *result = NULL;
+ MYSQL_ROW row = NULL;
+
+ if (!name)
+ return para_strdup("(none)");
+ stream = get_current_stream();
+ meta = get_meta(name, 1);
+ atts = get_atts(name, 0);
+ dir = get_dir(name);
+ /* get score */
+ query = get_query(stream, name, 0);
+ if (!query)
+ goto write;
+ result = get_result(query);
+ free(query);
+ if (result)
+ row = mysql_fetch_row(result);
+write:
+ info = make_message("dbinfo1:dir: %s\n"
+ "dbinfo2:stream: %s, %s, score: %s\n"
+ "dbinfo3:%s\n",
+ dir? dir : "(not contained in table)",
+ stream, meta,
+ (result && row && row[0])? row[0] : "(no score)",
+ atts);
+ if (dir)
+ free(dir);
+ if (meta)
+ free(meta);
+ if (atts)
+ free(atts);
+ if (stream)
+ free(stream);
+ if (result)
+ mysql_free_result(result);
+ return info;
+}
+
+
+/* might return NULL */
+static char *get_current_audio_file(void)
+{
+ char *name;
+ mmd_lock();
+ name = para_basename(mmd->filename);
+ mmd_unlock();
+ return name;
+}
+
+
+/* print database info */
+static int com_info(int fd, int argc, char *argv[])
+{
+ char *name = NULL, *meta = NULL, *atts = NULL, *dir = NULL;
+ int ret, com_la = strcmp(argv[0], "info");
+
+ if (argc < 1) {
+ ret = -E_GET_AUDIO_FILE;
+ if (!(name = get_current_audio_file()))
+ goto out;
+ ret = send_va_buffer(fd, "%s\n", name);
+ if (ret < 0)
+ goto out;
+ } else {
+ ret = -E_ESCAPE;
+ if (!(name = escaped_basename(argv[1])))
+ goto out;
+ }
+ meta = get_meta(name, 1);
+ atts = get_atts(name, 0);
+ dir = get_dir(name);
+ if (com_la)
+ ret = send_va_buffer(fd, "%s\n", atts);
+ else
+ ret = send_va_buffer(fd, "dir: %s\n" "%s\n" "attributes: %s\n",
+ dir? dir : "(not contained in table)", meta, atts);
+out:
+ if (meta)
+ free(meta);
+ if (atts)
+ free(atts);
+ if (dir)
+ free(dir);
+ if (name)
+ free(name);
+ return ret;
+}
+static int change_stream(char *stream)
+{
+ char *query;
+ int ret;
+ /* try to insert if it does not exist (compatibility) */
+// query = make_message("insert into streams (name, def) values "
+// "('current_stream', '%s')", stream);
+// real_query(query); /* ignore return value */
+// free(query);
+ query = make_message("update streams set def='%s' "
+ "where name = 'current_stream'", stream);
+ ret = real_query(query);
+ free(query);
+ return ret;
+}
+
+static int get_pic_id_by_name(char *name)
+{
+ char *q, *ebn;
+ void *result = NULL;
+ long unsigned ret;
+ MYSQL_ROW row;
+
+ if (!(ebn = escaped_basename(name)))
+ return -E_ESCAPE;
+ q = make_message("select pic_id from data where name = '%s'", ebn);
+ free(ebn);
+ result = get_result(q);
+ free(q);
+ if (!result)
+ return -E_NORESULT;
+ row = mysql_fetch_row(result);
+ ret = -E_NOROW;
+ if (row && row[0])
+ ret = atol(row[0]);
+ mysql_free_result(result);
+ return ret;
+}
+
+static int remove_entry(const char *name)
+{
+ char *q, *ebn = escaped_basename(name);
+ int ret = -E_ESCAPE;
+
+ if (!ebn || !*ebn)
+ goto out;
+ q = make_message("delete from data where name = '%s'", ebn);
+ real_query(q); /* ignore errors */
+ free(q);
+ q = make_message("delete from dir where name = '%s'", ebn);
+ real_query(q); /* ignore errors */
+ free(q);
+ ret = 1;
+out:
+ free(ebn);
+ return ret;
+}
+
+static int add_entry(const char *name)
+{
+ char *q, *dn, *ebn = NULL, *edn = NULL;
+ int ret;
+
+ if (!name || !*name)
+ return -E_MYSQL_SYNTAX;
+ ebn = escaped_basename(name);
+ if (!ebn)
+ return -E_ESCAPE;
+ ret = -E_MYSQL_SYNTAX;
+ dn = para_dirname(name);
+ if (!dn)
+ goto out;
+ ret = -E_ESCAPE;
+ edn = escape_str(dn);
+ free(dn);
+ if (!edn || !*edn)
+ goto out;
+ q = make_message("insert into data (name, pic_id) values "
+ "('%s', '%s')", ebn, "1");
+ ret = real_query(q);
+// ret = 1; PARA_DEBUG_LOG("q: %s\n", q);
+ free(q);
+ if (ret < 0)
+ goto out;
+ q = make_message("insert into dir (name, dir) values "
+ "('%s', '%s')", ebn, edn);
+// ret = 1; PARA_DEBUG_LOG("q: %s\n", q);
+ ret = real_query(q);
+ free(q);
+out:
+ if (ebn)
+ free(ebn);
+ if (edn)
+ free(edn);
+ return ret;
+}
+
+/*
+ * remove/add entries
+ */
+static int com_rm_ne(__unused int fd, int argc, char *argv[])
+{
+ int ne = !strcmp(argv[0], "ne");
+ int i, ret;
+ if (argc < 1)
+ return -E_MYSQL_SYNTAX;
+ for (i = 1; i <= argc; i++) {
+ ret = remove_entry(argv[i]);
+ if (ret < 0)
+ return ret;
+ if (!ne)
+ continue;
+ ret = add_entry(argv[i]);
+ if (ret < 0)
+ return ret;
+ }
+ return 1;
+}
+
+/*
+ * mv: rename entry
+ */
+static int com_mv(__unused int fd, int argc, char *argv[])
+{
+ char *q, *dn, *ebn1 = NULL, *ebn2 = NULL, *edn = NULL;
+ int ret;
+
+ if (argc != 2)
+ return -E_MYSQL_SYNTAX;
+ ebn1 = escaped_basename(argv[1]);
+ ebn2 = escaped_basename(argv[2]);
+ dn = para_dirname(argv[2]);
+ edn = escape_str(dn);
+ free(dn);
+ ret = -E_ESCAPE;
+ if (!ebn1 || !ebn2)
+ goto out;
+ remove_entry(ebn2);
+ q = make_message("update data set name = '%s' where name = '%s'",
+ ebn2, ebn1);
+ ret = real_query(q);
+ free(q);
+ if (ret < 0)
+ goto out;
+ q = make_message("update dir set name = '%s' where name = '%s'",
+ ebn2, ebn1);
+ ret = real_query(q);
+ free(q);
+ if (ret < 0)
+ goto out;
+ /* do not touch table dir, return success if argv[2] is no full path */
+ ret = 1;
+ if (!edn || !*edn)
+ goto out;
+ q = make_message("update dir set dir = '%s' where name = '%s'",
+ edn, ebn2);
+// PARA_DEBUG_LOG("q: %s\n", q);
+ ret = real_query(q);
+ free(q);
+out:
+ if (ebn1)
+ free(ebn1);
+ if (ebn2)
+ free(ebn2);
+ if (edn)
+ free(edn);
+ return ret;
+
+}
+
+/*
+ * picass: associate pic to audio file
+ * snp: set numplayed
+ */
+static int com_set(__unused int fd, int argc, char *argv[])
+{
+ char *q, *ebn;
+ long unsigned id;
+ int i, ret;
+ char *field = strcmp(argv[0], "picass")? "numplayed" : "pic_id";
+
+ if (argc < 2)
+ return -E_MYSQL_SYNTAX;
+ id = atol(argv[1]);
+ for (i = 2; i <= argc; i++) {
+ ebn = escaped_basename(argv[i]);
+ if (!ebn)
+ return -E_ESCAPE;
+ q = make_message("update data set %s = %lu "
+ "where name = '%s'", field, id, ebn);
+ free(ebn);
+ ret = real_query(q);
+ free(q);
+ if (ret < 0)
+ return ret;
+ }
+ return 1;
+}
+
+/*
+ * picch: change entry's name in pics table
+ */
+static int com_picch(__unused int fd, int argc, char *argv[])
+{
+ int ret;
+ long unsigned id;
+ char *q;
+
+ if (argc != 2)
+ return -E_MYSQL_SYNTAX;
+ id = atol(argv[1]);
+ if (strlen(argv[2]) > MAXLINE)
+ return -E_NAMETOOLONG;
+ q = make_message("update pics set name = '%s' where id = %lu", argv[2], id);
+ ret = real_query(q);
+ free(q);
+ return ret;
+}
+
+/*
+ * piclist: print list of pics in db
+ */
+static int com_piclist(__unused int fd, int argc, __unused char *argv[])
+{
+ void *result = NULL;
+ MYSQL_ROW row;
+ unsigned long *length;
+ int ret;
+
+ if (argc)
+ return -E_MYSQL_SYNTAX;
+ result = get_result("select id,name,pic from pics order by id");
+ if (!result)
+ return -E_NORESULT;
+ while ((row = mysql_fetch_row(result))) {
+ length = mysql_fetch_lengths(result);
+ if (!row || !row[0] || !row[1] || !row[2])
+ continue;
+ ret = send_va_buffer(fd, "%s\t%lu\t%s\n", row[0], length[2], row[1]);
+ if (ret < 0)
+ goto out;
+ }
+ ret = 1;
+out:
+ mysql_free_result(result);
+ return ret;
+}
+
+/*
+ * picdel: delete picture from database
+ */
+static int com_picdel(int fd, int argc, char *argv[])
+{
+ char *q;
+ long unsigned id;
+ my_ulonglong aff;
+ int i, ret;
+
+ if (argc < 1)
+ return -E_MYSQL_SYNTAX;
+ for (i = 1; i <= argc; i++) {
+ id = atol(argv[i]);
+ q = make_message("delete from pics where id = %lu", id);
+ ret = real_query(q);
+ free(q);
+ if (ret < 0)
+ return ret;
+ aff = mysql_affected_rows(mysql_ptr);
+ if (!aff) {
+ ret = send_va_buffer(fd, "No such id: %lu\n", id);
+ if (ret < 0)
+ return ret;
+ continue;
+ }
+ q = make_message("update data set pic_id = 1 where pic_id = %lu", id);
+ ret = real_query(q);
+ free(q);
+ }
+ return 1;
+}
+/*
+ * pic: get picture by name or by number
+ */
+static int com_pic(int fd, int argc, char *argv[])
+{
+ void *result = NULL;
+ MYSQL_ROW row;
+ unsigned long *length, id;
+ int ret;
+ char *q, *name = NULL;
+
+ if (argc < 1) {
+ ret = -E_GET_AUDIO_FILE;
+ name = get_current_audio_file();
+ } else {
+ ret = -E_ESCAPE;
+ name = escaped_basename(argv[1]);
+ }
+ if (!name)
+ return ret;
+ if (*name == '#')
+ id = atoi(name + 1);
+ else
+ id = get_pic_id_by_name(name);
+ free(name);
+ if (id <= 0)
+ return id;
+ q = make_message("select pic from pics where id = '%lu'", id);
+ result = get_result(q);
+ free(q);
+ if (!result)
+ return -E_NORESULT;
+ row = mysql_fetch_row(result);
+ ret = -E_NOROW;
+ if (!row || !row[0])
+ goto out;
+ length = mysql_fetch_lengths(result);
+ ret = send_bin_buffer(fd, row[0], *length);
+out:
+ mysql_free_result(result);
+ return ret;
+}
+
+/* strdel */
+static int com_strdel(__unused int fd, int argc, char *argv[])
+{
+ char *tmp;
+ int ret = -1;
+
+ if (argc < 1)
+ return -E_MYSQL_SYNTAX;
+ tmp = make_message("delete from streams where name='%s'", argv[1]);
+ ret = real_query(tmp);
+ free(tmp);
+ if (ret < 0)
+ return ret;
+ tmp = get_current_stream();
+ ret = 1;
+ if (strcmp(tmp, "(none)") && !strcmp(tmp, argv[1]))
+ ret = change_stream("(none)");
+ return ret;
+}
+
+/*
+ * ls
+ */
+static int com_ls(int fd, int argc, char *argv[])
+{
+ char *q;
+ void *result;
+ int ret;
+ unsigned int num_rows;
+
+ if (argc > 0)
+ q = make_message("select name from data where name LIKE '%s'",
+ argv[1]);
+ else
+ q = para_strdup("select name from data");
+ result = get_result(q);
+ free(q);
+ if (!result)
+ return -E_NORESULT;
+ num_rows = mysql_num_rows(result);
+ ret = 1;
+ if (num_rows)
+ ret = print_results(fd, result, 0, 0, num_rows - 1, 0);
+ mysql_free_result(result);
+ return ret;
+}
+/*
+ * summary
+ */
+static int com_summary(__unused int fd, int argc, __unused char *argv[])
+{
+ MYSQL_ROW row;
+ MYSQL_ROW row2;
+ void *result;
+ void *result2 = NULL;
+ const char *fmt = "select count(name) from data where %s='1'";
+ int ret = -E_NORESULT;
+
+ if (argc)
+ return -E_MYSQL_SYNTAX;
+ result = get_all_attributes();
+ if (!result)
+ goto out;
+ while ((row = mysql_fetch_row(result))) {
+ char *buf;
+
+ ret = -E_NOROW;
+ if (!row[0])
+ goto out;
+ ret = -E_NORESULT;
+ buf = make_message(fmt, row[0]);
+ result2 = get_result(buf);
+ free(buf);
+ if (!result2)
+ goto out;
+ ret = -E_NOROW;
+ row2 = mysql_fetch_row(result2);
+ if (!row2 || !row2[0])
+ goto out;
+ ret = send_va_buffer(fd, "%s\t%s\n", row[0], row2[0]);
+ if (ret < 0)
+ goto out;
+ }
+ ret = 1;
+out:
+ if (result2)
+ mysql_free_result(result2);
+ if (result)
+ mysql_free_result(result);
+ return ret;
+}
+
+static int get_numplayed(char *name)
+{
+ void *result;
+ MYSQL_ROW row;
+ const char *fmt = "select numplayed from data where name = '%s'";
+ char *buf = make_message(fmt, name);
+ int ret = -E_NORESULT;
+
+ result = get_result(buf);
+ free(buf);
+ if (!result)
+ goto out;
+ ret = -E_NOROW;
+ row = mysql_fetch_row(result);
+ if (!row || !row[0])
+ goto out;
+ ret = atoi(row[0]);
+out:
+ if (result)
+ mysql_free_result(result);
+ return ret;
+}
+
+static int update_audio_file(char *name)
+{
+ int ret;
+ const char *fmt1 = "update data set lastplayed = now() where name = '%s'";
+ const char *fmt2 = "update data set numplayed = %i where name = '%s'";
+ char *q;
+ char *ebn = escaped_basename(name);
+
+ ret = -E_ESCAPE;
+ if (!ebn)
+ goto out;
+ q = make_message(fmt1, ebn);
+ ret = real_query(q);
+ free(q);
+ if (ret < 0)
+ goto out;
+ ret = get_numplayed(ebn);
+ if (ret < 0)
+ goto out;
+ q = make_message(fmt2, ret + 1, ebn);
+ ret = real_query(q);
+ free(q);
+out:
+ if (ebn)
+ free(ebn);
+ return ret;
+}
+/* If called as child, mmd_lock must be held */
+static void update_mmd(char *info)
+{
+ PARA_DEBUG_LOG("%s", "updating shared memory area\n");
+ strncpy(mmd->selector_info, info, MMD_INFO_SIZE - 1);
+ mmd->selector_info[MMD_INFO_SIZE - 1] = '\0';
+}
+
+static void update_audio_file_server_handler(char *name)
+{
+ char *info;
+ info = get_selector_info(name);
+ update_mmd(info);
+ free(info);
+ update_audio_file(name);
+}
+
+static int com_us(__unused int fd, int argc, char *argv[])
+{
+ if (argc != 1)
+ return -E_MYSQL_SYNTAX;
+ return update_audio_file(argv[1]);
+}
+
+static void refresh_selector_info(void)
+{
+ char *name = get_current_audio_file();
+ char *info;
+
+ if (!name)
+ return;
+ info = get_selector_info(name);
+ free(name);
+ mmd_lock();
+ update_mmd(info);
+ mmd_unlock();
+ free(info);
+}
+
+/* select previous/next stream */
+static int com_ps(__unused int fd, int argc, char *argv[])
+{
+ char *query, *stream = get_current_stream();
+ void *result = get_result("select name from streams");
+ MYSQL_ROW row;
+ int match = -1, ret, i;
+ unsigned int num_rows;
+
+ if (argc)
+ return -E_MYSQL_SYNTAX;
+ ret = -E_NORESULT;
+ if (!result)
+ goto out;
+ num_rows = mysql_num_rows(result);
+ ret = -E_EMPTY_RESULT;
+ if (num_rows < 2)
+ goto out;
+ ret = -E_NOROW;
+ for (i = 0; i < num_rows; i++) {
+ row = mysql_fetch_row(result);
+ if (!row || !row[0])
+ goto out;
+ if (!strcmp(row[0], "current_stream"))
+ continue;
+ if (!strcmp(row[0], stream)) {
+ match = i;
+ break;
+ }
+ }
+ ret = -E_NO_STREAM;
+ if (match < 0)
+ goto out;
+ if (!strcmp(argv[0], "ps"))
+ i = match > 0? match - 1 : num_rows - 1;
+ else
+ i = match < num_rows - 1? match + 1 : 0;
+ ret = -E_NOROW;
+ mysql_data_seek(result, i);
+ row = mysql_fetch_row(result);
+ if (!row || !row[0])
+ goto out;
+ if (!strcmp(row[0], "current_stream")) {
+ if (!strcmp(argv[0], "ps")) {
+ i = match - 2;
+ i = i < 0? i + num_rows : i;
+ } else {
+ i = match + 2;
+ i = i > num_rows - 1? i - num_rows : i;
+ }
+ mysql_data_seek(result, i);
+ row = mysql_fetch_row(result);
+ if (!row || !row[0])
+ goto out;
+ }
+ query = make_message("update streams set def='%s' where name = "
+ "'current_stream'", row[0]);
+ ret = real_query(query);
+ free(query);
+ refresh_selector_info();
+out:
+ free(stream);
+ if (result)
+ mysql_free_result(result);
+ return ret;
+}
+
+/* streams */
+static int com_streams(int fd, int argc, __unused char *argv[])
+{
+ unsigned int num_rows;
+ int i, ret = -E_NORESULT;
+ void *result;
+ MYSQL_ROW row;
+
+ if (argc && strcmp(argv[1], "current_stream"))
+ return -E_MYSQL_SYNTAX;
+ if (argc) {
+ char *cs = get_current_stream();
+ ret = send_va_buffer(fd, "%s\n", cs);
+ free(cs);
+ return ret;
+ }
+ result = get_result("select name from streams");
+ if (!result)
+ goto out;
+ num_rows = mysql_num_rows(result);
+ ret = 1;
+ if (!num_rows)
+ goto out;
+ ret = -E_NOROW;
+ for (i = 0; i < num_rows; i++) {
+ row = mysql_fetch_row(result);
+ if (!row || !row[0])
+ goto out;
+ if (strcmp(row[0], "current_stream"))
+ send_va_buffer(fd, "%s\n", row[0]);
+ }
+ ret = 1;
+out:
+ if (result)
+ mysql_free_result(result);
+ return ret;
+}
+
+/* query stream definition */
+static int com_strq(int fd, int argc, char *argv[])
+{
+ MYSQL_ROW row;
+ char *query, *name;
+ void *result;
+ int ret;
+
+ if (argc < 1) {
+ ret = -E_GET_STREAM;
+ name = get_current_stream();
+ } else {
+ ret = -E_ESCAPE;
+ name = escaped_basename(argv[1]);
+ }
+ if (!name)
+ return ret;
+ ret = -E_NORESULT;
+ query = make_message("select def from streams where name='%s'", name);
+ free(name);
+ result = get_result(query);
+ free(query);
+ if (!result)
+ goto out;
+ ret = -E_NOROW;
+ row = mysql_fetch_row(result);
+ if (!row || !row[0])
+ goto out;
+ /* no '\n' needed */
+ ret = send_buffer(fd, row[0]);
+out:
+ if (result)
+ mysql_free_result(result);
+ return ret;
+}
+
+/* change stream / change stream and play */
+static int com_cs(int fd, int argc, char *argv[])
+{
+ int ret, stream_change;
+ char *query;
+ char *old_stream = get_current_stream();
+ int csp = !strcmp(argv[0], "csp");
+
+ if (!argc) {
+ ret = -E_MYSQL_SYNTAX;
+ if (csp)
+ goto out;
+ ret = send_va_buffer(fd, "%s\n", old_stream);
+ goto out;
+ }
+ ret = -E_GET_QUERY;
+ query = get_query(argv[1], NULL, 0); /* test if stream is valid */
+ if (!query)
+ goto out;
+ free(query);
+ /* stream is ok */
+ stream_change = strcmp(argv[1], old_stream);
+ if (stream_change) {
+ ret = change_stream(argv[1]);
+ if (ret < 0)
+ goto out;
+ refresh_selector_info();
+ }
+ if (csp) {
+ mmd_lock();
+ mmd->new_afs_status_flags |= AFS_PLAYING;
+ if (stream_change)
+ mmd->new_afs_status_flags |= AFS_NEXT;
+ mmd_unlock();
+ }
+ ret = 1;
+out:
+ free(old_stream);
+ return ret;
+}
+
+/*
+ * sl/skip
+ */
+static int com_sl(int fd, int argc, char *argv[])
+{
+ void *result = NULL;
+ MYSQL_ROW row;
+ int ret, i, skip = !strcmp(argv[0], "skip");
+ char *query, *stream, *tmp;
+ unsigned int num_rows, num;
+
+ if (argc < 1)
+ return -E_MYSQL_SYNTAX;
+ num = atoi(argv[1]);
+ if (!num)
+ return -E_MYSQL_SYNTAX;
+ stream = (argc == 1)? get_current_stream() : para_strdup(argv[2]);
+ tmp = get_query(stream, NULL, 0);
+ query = make_message("%s limit %d", tmp, num);
+ free(tmp);
+ ret = -E_GET_QUERY;
+ free(stream);
+ if (!query)
+ goto out;
+ ret = -E_NORESULT;
+ result = get_result(query);
+ free(query);
+ if (!result)
+ goto out;
+ ret = -E_EMPTY_RESULT;
+ num_rows = mysql_num_rows(result);
+ if (!num_rows)
+ goto out;
+ for (i = 0; i < num_rows && i < num; i++) {
+ row = mysql_fetch_row(result);
+ if (skip) {
+ send_va_buffer(fd, "Skipping %s\n", row[0]);
+ update_audio_file(row[0]);
+ } else
+ send_va_buffer(fd, "%s\n", row[0]? row[0]: "BUG");
+ }
+ ret = 1;
+out:
+ if (result)
+ mysql_free_result(result);
+ return ret;
+}
+
+/*
+ * update attributes of name
+ */
+static int update_atts(int fd, char *name, char *atts)
+{
+ int ret;
+ char *ebn, *q, *old, *new = NULL;
+
+ if (!mysql_ptr)
+ return -E_NOTCONN;
+ ebn = escaped_basename(name);
+ if (!ebn)
+ return -E_ESCAPE;
+ q = make_message("update data set %s where name = '%s'", atts, ebn);
+ old = get_atts(ebn, 0);
+ send_va_buffer(fd, "old: %s\n", old);
+ free(old);
+ ret = real_query(q);
+ free(q);
+ if (ret < 0)
+ goto out;
+ new = get_atts(ebn, 0);
+ ret = send_va_buffer(fd, "new: %s\n", new);
+ free(new);
+out:
+ free(ebn);
+ return ret;
+}
+
+/*
+ * set attributes
+ */
+static int com_sa(int fd, int argc, char *argv[])
+{
+ int i, ret;
+ char *atts = NULL, *name;
+
+ if (argc < 1)
+ return -E_MYSQL_SYNTAX;
+ for (i = 1; i <= argc; i++) {
+ int unset = 0;
+ char *tmp, *p =argv[i];
+ int len = strlen(p);
+
+ if (!len)
+ continue;
+ switch (p[len - 1]) {
+ case '+':
+ unset = 0;
+ break;
+ case '-':
+ unset = 1;
+ break;
+ default:
+ goto no_more_atts;
+ }
+ p[len - 1] = '\0';
+ tmp = make_message("%s%s='%s'", atts? "," : "", p,
+ unset? "0" : "1");
+ atts = para_strcat(atts, tmp);
+ free(tmp);
+ }
+no_more_atts:
+ if (!atts)
+ return -E_NOATTS;
+ if (i > argc) { /* no name given, use current af */
+ ret = -E_GET_AUDIO_FILE;
+ if (!(name = get_current_audio_file()))
+ goto out;
+ ret = update_atts(fd, name, atts);
+ free(name);
+ } else {
+ ret = 1;
+ for (; argv[i] && ret >= 0; i++)
+ ret = update_atts(fd, argv[i], atts);
+ }
+ refresh_selector_info();
+out:
+ return ret;
+}
+
+/*
+ * copy attributes
+ */
+static int com_cam(int fd, int argc, char *argv[])
+{
+ char *name = NULL, *meta = NULL, *atts = NULL;
+ int i, ret;
+
+ if (argc < 2)
+ return -E_MYSQL_SYNTAX;
+ if (!(name = escaped_basename(argv[1])))
+ return -E_ESCAPE;
+ ret = -E_NOATTS;
+ if (!(atts = get_atts(name, 1)))
+ goto out;
+ ret = -E_META;
+ if (!(meta = get_meta(name, 0)))
+ goto out;
+ for (i = 2; i <= argc; i++) {
+ char *ebn, *q;
+ ret = -E_ESCAPE;
+ if (!(ebn = escaped_basename(argv[i])))
+ goto out;
+ ret = send_va_buffer(fd, "updating %s\n", ebn);
+ if (ret < 0) {
+ free(ebn);
+ goto out;
+ }
+ q = make_message("update data set %s where name = '%s'",
+ meta, ebn);
+ if ((ret = update_atts(fd, ebn, atts)) >= 0)
+ ret = real_query(q);
+ free(ebn);
+ free(q);
+ if (ret < 0)
+ goto out;
+ }
+ ret = 1;
+out:
+ if (name)
+ free(name);
+ if (meta)
+ free(meta);
+ if (atts)
+ free(atts);
+ return ret;
+}
+
+/*
+ * verify / clean
+ */
+static int com_vrfy(int fd, int argc, __unused char *argv[])
+{
+ char *query;
+ int ret, vrfy_mode = strcmp(argv[0], "clean");
+ void *result = NULL;
+ unsigned int num_rows;
+ MYSQL_ROW row;
+ char *escaped_name;
+
+ if (argc)
+ return -E_MYSQL_SYNTAX;
+ ret = -E_NORESULT;
+ result = get_result("select data.name from data left join dir on "
+ "dir.name = data.name where dir.name is NULL");
+ if (!result)
+ goto out;
+ num_rows = mysql_num_rows(result);
+ if (!num_rows) {
+ ret = send_buffer(fd, "No invalid entries\n");
+ goto out;
+ }
+ if (vrfy_mode) {
+ send_va_buffer(fd, "found %i invalid entr%s\n", num_rows,
+ num_rows == 1? "y" : "ies");
+ ret = print_results(fd, result, 0, 0, num_rows - 1, 0);
+ goto out;
+ }
+ while ((row = mysql_fetch_row(result))) {
+ ret = -E_NOROW;
+ if (!row[0])
+ goto out;
+ ret = -E_ESCAPE;
+ escaped_name = escape_str(row[0]);
+ if (!escaped_name)
+ goto out;
+ send_va_buffer(fd, "deleting %s\n", escaped_name);
+ query = make_message("delete from data where name = '%s'",
+ escaped_name);
+ ret = real_query(query);
+ free(query);
+ if (ret < 0)
+ goto out;
+ }
+
+out:
+ if (result)
+ mysql_free_result(result);
+ return ret;
+}
+
+static FILE *out_file;
+
+static int mysql_write_tmp_file(const char *dir, const char *name)
+{
+ int ret = -E_TMPFILE;
+ char *msg = make_message("%s\t%s\n", dir, name);
+
+ if (fputs(msg, out_file) != EOF)
+ ret = 1;
+ free(msg);
+ return ret;
+}
+
+/*
+ * update database
+ */
+static int com_upd(int fd, int argc, __unused char *argv[])
+{
+ char *tempname = NULL, *query = NULL;
+ int ret, out_fd = -1, num = 0;
+ void *result = NULL;
+ unsigned int num_rows;
+ MYSQL_ROW row;
+
+ if (argc)
+ return -E_MYSQL_SYNTAX;
+ out_file = NULL;
+ tempname = para_strdup("/tmp/mysql.tmp.XXXXXX");
+ ret = para_mkstemp(tempname, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ if (ret < 0)
+ goto out;
+ out_fd = ret;
+ out_file = fdopen(out_fd, "w");
+ if (!out_file) {
+ close(out_fd);
+ goto out;
+ }
+ if (find_audio_files(conf.mysql_audio_file_dir_arg, mysql_write_tmp_file) < 0)
+ goto out;
+ num = ftell(out_file);
+ /*
+ * we have to make sure the file hit the disk before we call
+ * real_query
+ */
+ fclose(out_file);
+ out_file = NULL;
+ PARA_DEBUG_LOG("wrote tempfile %s (%d bytes)\n", tempname, num);
+ if (!num)
+ goto out;
+ if ((ret = real_query("delete from dir")) < 0)
+ goto out;
+ query = make_message("load data infile '%s' into table dir "
+ "fields terminated by '\t' lines terminated by '\n' "
+ "(dir, name)", tempname);
+ ret = real_query(query);
+ free(query);
+ if (ret < 0)
+ goto out;
+ result = get_result("select dir.name from dir left join data on "
+ "data.name = dir.name where data.name is NULL");
+ ret = -E_NORESULT;
+ if (!result)
+ goto out;
+ num_rows = mysql_num_rows(result);
+ if (!num_rows) {
+ ret = send_buffer(fd, "no new entries\n");
+ goto out;
+ }
+ while ((row = mysql_fetch_row(result))) {
+ ret = -E_NOROW;
+ if (!row[0])
+ goto out;
+ send_va_buffer(fd, "new entry: %s\n", row[0]);
+ query = make_message("insert into data (name, pic_id) values "
+ "('%s','%s')", row[0], "1");
+ ret = real_query(query);
+ free(query);
+ if (ret < 0)
+ goto out;
+ }
+ ret = 1;
+out:
+ if (out_fd >= 0)
+ unlink(tempname);
+ free(tempname);
+ if (out_file)
+ fclose(out_file);
+ if (result)
+ mysql_free_result(result);
+ return ret;
+}
+
+static char **server_get_audio_file_list(unsigned int num)
+{
+ char **list = para_malloc((num + 1) * sizeof(char *));
+ char *tmp, *query, *stream = get_current_stream();
+ void *result = NULL;
+ unsigned int num_rows;
+ int i = 0;
+ MYSQL_ROW row;
+
+ tmp = get_query(stream, NULL, 1);
+ free(stream);
+ query = make_message("%s limit %d", tmp, num);
+ free(tmp);
+ if (!query)
+ goto err_out;
+ result = get_result(query);
+ if (!result)
+ goto err_out;
+ num_rows = mysql_num_rows(result);
+ if (!num_rows)
+ goto err_out;
+ for (i = 0; i < num_rows && i < num; i++) {
+ row = mysql_fetch_row(result);
+ if (!row || !row[0])
+ goto err_out;
+ list[i] = para_strdup(row[0]);
+ }
+ list[i] = NULL;
+ goto success;
+err_out:
+ while (i > 0) {
+ i--;
+ free(list[i]);
+ }
+ free(list);
+ list = NULL;
+success:
+ if (query)
+ free(query);
+ if (result)
+ mysql_free_result(result);
+ return list;
+}
+
+/*
+ * connect to mysql server, return mysql pointer on success, -E_NOTCONN
+ * on errors. Called from parent on startup and also from com_cdb().
+ */
+static int init_mysql_server(void)
+{
+ char *u = conf.mysql_user_arg? conf.mysql_user_arg : para_logname();
+
+ mysql_ptr = mysql_init(NULL);
+ if (!mysql_ptr) {
+ PARA_CRIT_LOG("%s", "mysql init error\n");
+ return -E_NOTCONN;
+ }
+ PARA_DEBUG_LOG("connecting: %s@%s:%d\n", u, conf.mysql_host_arg,
+ conf.mysql_port_arg);
+ if (!conf.mysql_user_arg)
+ free(u);
+ /*
+ * If host is NULL a connection to the local host is assumed,
+ * If user is NULL, the current user is assumed
+ */
+ if (!(mysql_ptr = mysql_real_connect(mysql_ptr,
+ conf.mysql_host_arg,
+ conf.mysql_user_arg,
+ conf.mysql_passwd_arg,
+ conf.mysql_database_arg,
+ conf.mysql_port_arg, NULL, 0))) {
+ PARA_CRIT_LOG("%s", "connect error\n");
+ return -E_NOTCONN;
+ }
+ PARA_INFO_LOG("%s", "success\n");
+ return 1;
+}
+
+/* mmd lock must be held */
+static void write_msg2mmd(int success)
+{
+ sprintf(mmd->selector_info, "dbinfo1:%s\ndbinfo2:mysql-%s\ndbinfo3:\n",
+ success < 0? PARA_STRERROR(-success) :
+ "successfully connected to mysql server",
+ success < 0? "" : mysql_get_server_info(mysql_ptr));
+}
+
+/* create database */
+static int com_cdb(int fd, int argc, char *argv[])
+{
+ char *query, *name;
+ int ret;
+
+ if (argc < 1)
+ name = "paraslash";
+ else {
+ ret = -E_NAMETOOLONG;
+ name = argv[1];
+ if (strlen(name) > MAXLINE)
+ goto out;
+ }
+ if (mysql_ptr) {
+ PARA_INFO_LOG("%s", "closing database\n");
+ mysql_close(mysql_ptr);
+ }
+ /* dont use any database */
+ conf.mysql_database_arg = NULL; /* leak? */
+ ret = -E_MYSQL_INIT;
+ if (init_mysql_server() < 0 || !mysql_ptr)
+ goto out;
+ query = make_message("create database %s", name);
+ ret = real_query(query);
+ free(query);
+ if (ret < 0)
+ goto out;
+ /* reconnect with database just created */
+ mysql_close(mysql_ptr);
+ conf.mysql_database_arg = para_strdup(name);
+ ret = -E_MYSQL_INIT;
+ if (init_mysql_server() < 0 || !mysql_ptr)
+ goto out;
+ mmd_lock();
+ write_msg2mmd(1);
+ mmd_unlock();
+ ret = -E_QFAILED;
+ if (real_query("create table data (name varchar(255) binary not null "
+ "primary key, "
+ "lastplayed datetime not null default "
+ "'1970-01-01', "
+ "numplayed int not null default 0, "
+ "pic_id bigint unsigned not null default 1)") < 0)
+ goto out;
+ if (real_query("create table dir (name varchar(255) binary not null "
+ "primary key, dir varchar(255) default null)") < 0)
+ goto out;
+ if (real_query("create table pics ("
+ "id bigint(20) unsigned not null primary key "
+ "auto_increment, "
+ "name varchar(255) binary not null, "
+ "pic mediumblob not null)") < 0)
+ goto out;
+ if (real_query("create table streams ("
+ "name varchar(255) binary not null primary key, "
+ "def blob not null)") < 0)
+ goto out;
+ if (real_query("insert into streams (name, def) values "
+ "('current_stream', '(none)')") < 0)
+ goto out;
+ ret = send_va_buffer(fd, "successfully created database %s\n", name);
+out:
+ return ret;
+}
+
+static void shutdown_connection(void)
+{
+ if (mysql_ptr) {
+ PARA_NOTICE_LOG("%s", "shutting down mysql connection\n");
+ mysql_close(mysql_ptr);
+ mysql_ptr = NULL;
+ }
+}
+
+/**
+ * the init function of the mysql-based audio file selector
+ *
+ * Check the command line options and initialize all function pointers of \a db.
+ * Connect to the mysql server and initialize the info string.
+ *
+ * \sa struct audio_file_selector, misc_meta_data::selector_info,
+ * random_selector.c
+ */
+int mysql_selector_init(struct audio_file_selector *db)
+{
+ int ret;
+
+ if (!conf.mysql_passwd_given)
+ return -E_NO_MYSQL_PASSWD;
+ if (!conf.mysql_audio_file_dir_given)
+ return -E_NO_AF_DIR;
+ db->name = "mysql";
+ db->cmd_list = cmds;
+ db->get_audio_file_list = server_get_audio_file_list;
+ db->update_audio_file = update_audio_file_server_handler;
+ db->shutdown = shutdown_connection;
+ ret = init_mysql_server();
+ if (ret < 0)
+ PARA_WARNING_LOG("%s\n", PARA_STRERROR(-ret));
+ write_msg2mmd(ret);
+ return 1; /* return success even if connect failed to give the
+ * user the chance to exec com_cdb
+ */
+}
static FILE *infile;
static int header_len, oggbuf_len, vi_channels;
static char *header, *oggbuf;
-static ogg_int64_t *chunk_table, max_chunk_len;
+static ssize_t *chunk_table, max_chunk_len;
struct audio_format *af;
static long vi_sampling_rate, vi_bitrate, vi_bitrate_nominal,
num_chunks;
static void ogg_compute_chunk_table(double time_total)
{
int i, ret, num;
- ogg_int64_t pos = 0, min = 0, old_pos;
+ ssize_t pos = 0, min = 0, old_pos;
old_pos = 0;
ret = 0;
min = (i == 1)? diff : MIN(min, diff);
chunk_table[i] = pos;
if (i < 11 || !((i - 1) % 1000)|| i > num - 11)
- PARA_DEBUG_LOG("chunk #%d: %g secs, pos: %lli, "
- "size: %lli\n", i - 1,
+ PARA_DEBUG_LOG("chunk #%d: %g secs, pos: %zd, "
+ "size: %zd\n", i - 1,
i * chunk_time, pos, pos - old_pos);
old_pos = pos;
}
num_chunks = i - 1;
chunk_table[i] = pos;
tunetable();
- PARA_INFO_LOG("%li chunks (%fs), max chunk: %lli, min chunk: %lli\n",
+ PARA_INFO_LOG("%li chunks (%fs), max chunk: %zd, min chunk: %zd\n",
num_chunks, chunk_time, max_chunk_len, min);
rewind(infile);
}
SI_DBINFO3, SI_DECODER_FLAGS, SI_AUDIOD_STATUS,
SI_PLAY_TIME, SI_UPTIME, SI_OFFSET,
SI_LENGTH, SI_STREAM_START, SI_CURRENT_TIME,
- SI_AUDIOD_UPTIME, SI_DBTOOL,
+ SI_AUDIOD_UPTIME, SI_SELECTOR, NUM_STAT_ITEMS
};
-#define NUM_STAT_ITEMS (SI_DBTOOL + 1)
+
int stat_line_valid(const char *);
void stat_client_write(char *msg);
int stat_client_add(int);
--- /dev/null
+/*
+ * Copyright (C) 2006 Andre Noll <maan@systemlinux.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, USA.
+ */
+
+/** \file playlist_selector.c The playlist audio file selector of paraslash */
+
+#include "server.h"
+#include "db.h"
+#include "error.h"
+#include "net.h"
+#include "string.h"
+#include "ipc.h"
+
+/**
+ * structure used for transmission of the playlist
+ *
+ * There's one such struct which gets initialized during startup. It lives in
+ * shared memory and is used by com_lpl().
+ */
+struct pls_client_data {
+/** allocated and set by com_lpl() (child) */
+ int shm_id;
+/** the size of the shared memory area identified by \a shm_id */
+ size_t size;
+/** initially locked, gets unlocked by parent when it is done */
+ int mutex;
+/** return value, set by parent */
+ int retval;
+};
+
+/** data specific to the playlist selector */
+struct private_pls_data {
+/** guards against concurrent client access */
+ int client_mutex;
+/** guards against concurrent parent-child access */
+ int server_mutex;
+/** pointer to the client data */
+ struct pls_client_data *client_data;
+/** id of the shm corresponding to \a client_data */
+ int client_data_shm_id;
+};
+
+/** we refuse to load playlists bigger than that */
+#define MAX_PLAYLIST_BYTES (1024 * 1024)
+
+static unsigned playlist_len, playlist_size, current_playlist_entry;
+static char **playlist;
+static struct audio_file_selector *self;
+
+static int com_ppl(int, int, char **);
+static int com_lpl(int, int, char **);
+extern struct misc_meta_data *mmd;
+
+/* array of supported commands */
+static struct server_command cmds[] = {
+{
+.name = "ppl",
+.handler = com_ppl,
+.perms = DB_READ,
+.description = "print playlist",
+.synopsis = "ppl",
+.help =
+"Print out the current playlist"
+}, {
+.name = "lpl",
+.handler = com_lpl,
+.perms = DB_WRITE,
+.description = "load playlist",
+.synopsis = "lpl",
+.help =
+"Read a new playlist from stdin. Example:\n"
+"\tfind /audio -name '*.mp3' | para_client lpl"
+}, {
+.name = NULL,
+}
+};
+
+static void playlist_add(char *path)
+{
+ if (playlist_len >= playlist_size) {
+ playlist_size = 2 * playlist_size + 1;
+ playlist = para_realloc(playlist, playlist_size * sizeof(char *));
+ }
+ PARA_DEBUG_LOG("adding #%d/%d: %s\n", playlist_len, playlist_size, path);
+ playlist[playlist_len++] = para_strdup(path);
+}
+
+static int send_playlist_to_server(const char *buf, size_t size)
+{
+ struct private_pls_data *ppd = self->private_data;
+ int ret, shm_mutex = -1, shm_id = -1;
+ void *shm = NULL;
+
+ PARA_DEBUG_LOG("new playlist (%d bytes)\n", size);
+
+ ret = mutex_new();
+ if (ret < 0)
+ return ret;
+ shm_mutex = ret;
+
+ ret = shm_new(size);
+ if (ret < 0)
+ goto out;
+ shm_id = ret;
+
+ ret = shm_attach(shm_id, ATTACH_RW, &shm);
+ if (ret < 0)
+ goto out;
+ mutex_lock(shm_mutex);
+ memcpy(shm, buf, size);
+ mutex_lock(ppd->client_mutex);
+ mutex_lock(ppd->server_mutex);
+ ppd->client_data->size = size;
+ ppd->client_data->shm_id = shm_id;
+ ppd->client_data->mutex = shm_mutex;
+ kill(getppid(), SIGUSR1); /* wake up the server */
+ mutex_unlock(ppd->server_mutex);
+ mutex_lock(shm_mutex); /* wait until server is done */
+ mutex_unlock(shm_mutex);
+ ret = ppd->client_data->retval;
+ mutex_unlock(ppd->client_mutex);
+ shm_detach(shm);
+out:
+ if (shm_id >= 0)
+ shm_destroy(shm_id);
+ mutex_destroy(shm_mutex);
+ PARA_DEBUG_LOG("returning %d\n", ret);
+ return ret;
+}
+
+static int com_lpl(int fd, __unused int argc, __unused char *argv[])
+{
+ unsigned loaded = 0;
+ size_t bufsize = 4096; /* guess that's enough */
+ char *buf = para_malloc(bufsize);
+ ssize_t ret;
+ ret = send_buffer(fd, AWAITING_DATA_MSG);
+ if (ret < 0)
+ goto out;
+again:
+ ret = recv_bin_buffer(fd, buf + loaded, bufsize - loaded);
+ if (ret < 0)
+ goto out;
+ if (!ret) {
+ ret = send_playlist_to_server(buf, loaded);
+ goto out;
+ }
+ loaded += ret;
+ ret = -E_LOAD_PLAYLIST;
+ if (loaded >= MAX_PLAYLIST_BYTES)
+ goto out;
+ if (loaded >= bufsize) {
+ bufsize *= 2;
+ buf = para_realloc(buf, bufsize);
+ }
+ goto again;
+out:
+ free(buf);
+ return ret;
+}
+
+static int com_ppl(int fd, __unused int argc, __unused char *argv[])
+{
+ unsigned i;
+
+ PARA_DEBUG_LOG("sending playlist to client (%d entries)\n", playlist_len);
+ for (i = 0; i < playlist_len; i++) {
+ int ret = send_va_buffer(fd, "%s\n", playlist[
+ (i + current_playlist_entry) % playlist_len]);
+ if (ret < 0)
+ return ret;
+ }
+ return 1;
+}
+
+static char **pls_get_audio_file_list(unsigned int num)
+{
+ char **file_list;
+ unsigned i;
+
+ num = MIN(num, playlist_len);
+ if (!num)
+ return NULL;
+ file_list = para_malloc((num + 1) * sizeof(char *));
+ for (i = 0; i < num; i++) {
+ unsigned j = (current_playlist_entry + i) % playlist_len;
+ file_list[i] = para_strdup(playlist[j]);
+ }
+ file_list[i] = NULL;
+ return file_list;
+}
+
+static void free_playlist_contents(void)
+{
+ int i;
+
+ PARA_DEBUG_LOG("freeing playlist (%d entries)\n", playlist_len);
+ for (i = 0; i < playlist_len; i++)
+ free(playlist[i]);
+ current_playlist_entry = 0;
+ playlist_len = 0;
+}
+
+static void pls_shutdown(void)
+{
+ struct private_pls_data *ppd = self->private_data;
+
+ shm_detach(ppd->client_data);
+ shm_destroy(ppd->client_data_shm_id);
+ mutex_destroy(ppd->server_mutex);
+ mutex_destroy(ppd->client_mutex);
+ free(ppd);
+ free_playlist_contents();
+ free(playlist);
+ playlist = NULL;
+ playlist_len = 0;
+ playlist_size = 0;
+}
+
+static void pls_post_select(__unused fd_set *rfds, __unused fd_set *wfds)
+{
+ struct private_pls_data *ppd = self->private_data;
+ struct pls_client_data *pcd = ppd->client_data;
+ int ret;
+ void *shm;
+
+ mutex_lock(ppd->server_mutex);
+ if (!pcd->size)
+ goto out;
+ free_playlist_contents();
+ ret = shm_attach(pcd->shm_id, ATTACH_RW, &shm);
+ if (ret < 0) {
+ PARA_ERROR_LOG("%s\n", PARA_STRERROR(-ret));
+ goto out;
+ }
+ PARA_DEBUG_LOG("loading new playlist (%d bytes)\n", pcd->size);
+ ret = for_each_line((char *)shm, pcd->size, &playlist_add);
+ shm_detach(shm);
+ PARA_NOTICE_LOG("new playlist (%d entries)\n", playlist_len);
+ pcd->retval = 1;
+ pcd->size = 0;
+ mutex_unlock(pcd->mutex);
+out:
+ mutex_unlock(ppd->server_mutex);
+}
+
+void pls_update_audio_file(char *audio_file)
+{
+ unsigned i;
+
+ for (i = 0; i < playlist_len; i++) {
+ unsigned j = (current_playlist_entry + i) % playlist_len;
+ if (strcmp(playlist[j], audio_file))
+ continue;
+ current_playlist_entry = (j + 1) % playlist_len;
+ }
+}
+
+/**
+ * the init function for the playlist selector
+ *
+ * Init all function pointers of \a db
+ *
+ * \sa struct audio_file_selector, misc_meta_data::selector_info, mysql.c
+ * random_selector.c.
+ */
+int playlist_selector_init(struct audio_file_selector *db)
+{
+ int ret;
+ struct private_pls_data *ppd = NULL;
+ void *shm = NULL;
+
+ self = db;
+ db->cmd_list = cmds;
+ db->get_audio_file_list = pls_get_audio_file_list;
+ db->shutdown = pls_shutdown;
+ db->post_select = pls_post_select;
+ db->update_audio_file = pls_update_audio_file;
+ ppd = para_calloc(sizeof(struct private_pls_data));
+ db->private_data = ppd;
+
+ ppd->client_mutex = -1;
+ ppd->server_mutex = -1;
+ ppd->client_data_shm_id = -1;
+ ppd->client_data = NULL;
+
+ ret = mutex_new();
+ if (ret < 0)
+ goto err_out;
+ ppd->client_mutex = ret;
+
+ ret = mutex_new();
+ if (ret < 0)
+ goto err_out;
+ ppd->server_mutex = ret;
+
+ ret = shm_new(sizeof(struct pls_client_data));
+ if (ret < 0)
+ goto err_out;
+ ppd->client_data_shm_id = ret;
+
+ ret = shm_attach(ppd->client_data_shm_id, ATTACH_RW, &shm);
+ if (ret < 0)
+ goto err_out;
+ ppd->client_data = shm;
+ ppd->client_data->size = 0;
+ sprintf(mmd->selector_info, "playlist selector initialized");
+ return 1;
+err_out:
+ if (ppd->client_data_shm_id >= 0)
+ shm_destroy(ppd->client_data_shm_id);
+ if (ppd->client_mutex >= 0)
+ mutex_destroy(ppd->client_mutex);
+ if (ppd->server_mutex >= 0)
+ mutex_destroy(ppd->server_mutex);
+ free(ppd);
+ return ret;
+}
+++ /dev/null
-/*
- * Copyright (C) 2006 Andre Noll <maan@systemlinux.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, USA.
- */
-
-/** \file plm_dbtool.c Playlist manager for paraslash */
-
-#include "server.h"
-#include "db.h"
-#include "error.h"
-#include "net.h"
-#include "string.h"
-#include "ipc.h"
-
-/**
- * structure used for transmission of the playlist
- *
- * There's one such struct which gets initialized during startup. It lives in
- * shared memory and is used by com_lpl().
- */
-struct plm_client_data {
-/** allocated and set by com_lpl() (child) */
- int shm_id;
-/** the size of the shared memory area identified by \a shm_id */
- size_t size;
-/** initially locked, gets unlocked by parent when it is done */
- int mutex;
-/** return value, set by parent */
- int retval;
-};
-
-/** data specific to the plm database tool */
-struct private_plm_data {
-/** guards against concurrent client access */
- int client_mutex;
-/** guards against concurrent parent-child access */
- int server_mutex;
-/** pointer to the client data */
- struct plm_client_data *client_data;
-/** id of the shm corresponding to \a client_data */
- int client_data_shm_id;
-};
-
-/** we refuse to load playlists bigger than that */
-#define MAX_PLAYLIST_BYTES (1024 * 1024)
-
-static unsigned playlist_len, playlist_size, current_playlist_entry;
-static char **playlist;
-static struct dbtool *self;
-
-static int com_ppl(int, int, char **);
-static int com_lpl(int, int, char **);
-extern struct misc_meta_data *mmd;
-
-/* array of commands that are supported by this database tool */
-static struct server_command cmds[] = {
-{
-.name = "ppl",
-.handler = com_ppl,
-.perms = DB_READ,
-.description = "print playlist",
-.synopsis = "ppl",
-.help =
-"Print out the current playlist"
-}, {
-.name = "lpl",
-.handler = com_lpl,
-.perms = DB_WRITE,
-.description = "load playlist",
-.synopsis = "lpl",
-.help =
-"Read a new playlist from stdin. Example:\n"
-"\tfind /audio -name '*.mp3' | para_client lpl"
-}, {
-.name = NULL,
-}
-};
-
-static void playlist_add(char *path)
-{
- if (playlist_len >= playlist_size) {
- playlist_size = 2 * playlist_size + 1;
- playlist = para_realloc(playlist, playlist_size * sizeof(char *));
- }
- PARA_DEBUG_LOG("adding #%d/%d: %s\n", playlist_len, playlist_size, path);
- playlist[playlist_len++] = para_strdup(path);
-}
-
-static int send_playlist_to_server(const char *buf, size_t size)
-{
- struct private_plm_data *ppd = self->private_data;
- int ret, shm_mutex = -1, shm_id = -1;
- void *shm = NULL;
-
- PARA_DEBUG_LOG("new playlist (%d bytes)\n", size);
-
- ret = mutex_new();
- if (ret < 0)
- return ret;
- shm_mutex = ret;
-
- ret = shm_new(size);
- if (ret < 0)
- goto out;
- shm_id = ret;
-
- ret = shm_attach(shm_id, ATTACH_RW, &shm);
- if (ret < 0)
- goto out;
- mutex_lock(shm_mutex);
- memcpy(shm, buf, size);
- mutex_lock(ppd->client_mutex);
- mutex_lock(ppd->server_mutex);
- ppd->client_data->size = size;
- ppd->client_data->shm_id = shm_id;
- ppd->client_data->mutex = shm_mutex;
- kill(getppid(), SIGUSR1); /* wake up the server */
- mutex_unlock(ppd->server_mutex);
- mutex_lock(shm_mutex); /* wait until server is done */
- mutex_unlock(shm_mutex);
- ret = ppd->client_data->retval;
- mutex_unlock(ppd->client_mutex);
- shm_detach(shm);
-out:
- if (shm_id >= 0)
- shm_destroy(shm_id);
- mutex_destroy(shm_mutex);
- PARA_DEBUG_LOG("returning %d\n", ret);
- return ret;
-}
-
-static int com_lpl(int fd, __unused int argc, __unused char *argv[])
-{
- unsigned loaded = 0;
- size_t bufsize = 4096; /* guess that's enough */
- char *buf = para_malloc(bufsize);
- ssize_t ret;
- ret = send_buffer(fd, AWAITING_DATA_MSG);
- if (ret < 0)
- goto out;
-again:
- ret = recv_bin_buffer(fd, buf + loaded, bufsize - loaded);
- if (ret < 0)
- goto out;
- if (!ret) {
- ret = send_playlist_to_server(buf, loaded);
- goto out;
- }
- loaded += ret;
- ret = -E_LOAD_PLAYLIST;
- if (loaded >= MAX_PLAYLIST_BYTES)
- goto out;
- if (loaded >= bufsize) {
- bufsize *= 2;
- buf = para_realloc(buf, bufsize);
- }
- goto again;
-out:
- free(buf);
- return ret;
-}
-
-static int com_ppl(int fd, __unused int argc, __unused char *argv[])
-{
- unsigned i;
-
- PARA_DEBUG_LOG("sending playlist to client (%d entries)\n", playlist_len);
- for (i = 0; i < playlist_len; i++) {
- int ret = send_va_buffer(fd, "%s\n", playlist[
- (i + current_playlist_entry) % playlist_len]);
- if (ret < 0)
- return ret;
- }
- return 1;
-}
-
-static char **plm_get_audio_file_list(unsigned int num)
-{
- char **file_list;
- unsigned i;
-
- num = MIN(num, playlist_len);
- if (!num)
- return NULL;
- file_list = para_malloc((num + 1) * sizeof(char *));
- for (i = 0; i < num; i++) {
- unsigned j = (current_playlist_entry + i) % playlist_len;
- file_list[i] = para_strdup(playlist[j]);
- }
- file_list[i] = NULL;
- return file_list;
-}
-
-static void free_playlist_contents(void)
-{
- int i;
-
- PARA_DEBUG_LOG("freeing playlist (%d entries)\n", playlist_len);
- for (i = 0; i < playlist_len; i++)
- free(playlist[i]);
- current_playlist_entry = 0;
- playlist_len = 0;
-}
-
-static void plm_shutdown(void)
-{
- struct private_plm_data *ppd = self->private_data;
-
- shm_detach(ppd->client_data);
- shm_destroy(ppd->client_data_shm_id);
- mutex_destroy(ppd->server_mutex);
- mutex_destroy(ppd->client_mutex);
- free(ppd);
- free_playlist_contents();
- free(playlist);
- playlist = NULL;
- playlist_len = 0;
- playlist_size = 0;
-}
-
-static void plm_post_select(__unused fd_set *rfds, __unused fd_set *wfds)
-{
- struct private_plm_data *ppd = self->private_data;
- struct plm_client_data *pcd = ppd->client_data;
- int ret;
- void *shm;
-
- mutex_lock(ppd->server_mutex);
- if (!pcd->size)
- goto out;
- free_playlist_contents();
- ret = shm_attach(pcd->shm_id, ATTACH_RW, &shm);
- if (ret < 0) {
- PARA_ERROR_LOG("%s\n", PARA_STRERROR(-ret));
- goto out;
- }
- PARA_DEBUG_LOG("loading new playlist (%d bytes)\n", pcd->size);
- ret = for_each_line((char *)shm, pcd->size, &playlist_add);
- shm_detach(shm);
- PARA_NOTICE_LOG("new playlist (%d entries)\n", playlist_len);
- pcd->retval = 1;
- pcd->size = 0;
- mutex_unlock(pcd->mutex);
-out:
- mutex_unlock(ppd->server_mutex);
-}
-
-void plm_update_audio_file(char *audio_file)
-{
- unsigned i;
-
- for (i = 0; i < playlist_len; i++) {
- unsigned j = (current_playlist_entry + i) % playlist_len;
- if (strcmp(playlist[j], audio_file))
- continue;
- current_playlist_entry = (j + 1) % playlist_len;
- }
-}
-
-/**
- * the init function for the plm database tool
- *
- * Init all function pointers of \a db
- *
- * \sa struct dbtool, misc_meta_data::dbinfo, mysql.c random_dbtool.c
- */
-int plm_dbtool_init(struct dbtool *db)
-{
- int ret;
- struct private_plm_data *ppd = NULL;
- void *shm = NULL;
-
- self = db;
- db->cmd_list = cmds;
- db->get_audio_file_list = plm_get_audio_file_list;
- db->shutdown = plm_shutdown;
- db->post_select = plm_post_select;
- db->update_audio_file = plm_update_audio_file;
- ppd = para_calloc(sizeof(struct private_plm_data));
- db->private_data = ppd;
-
- ppd->client_mutex = -1;
- ppd->server_mutex = -1;
- ppd->client_data_shm_id = -1;
- ppd->client_data = NULL;
-
- ret = mutex_new();
- if (ret < 0)
- goto err_out;
- ppd->client_mutex = ret;
-
- ret = mutex_new();
- if (ret < 0)
- goto err_out;
- ppd->server_mutex = ret;
-
- ret = shm_new(sizeof(struct plm_client_data));
- if (ret < 0)
- goto err_out;
- ppd->client_data_shm_id = ret;
-
- ret = shm_attach(ppd->client_data_shm_id, ATTACH_RW, &shm);
- if (ret < 0)
- goto err_out;
- ppd->client_data = shm;
- ppd->client_data->size = 0;
- sprintf(mmd->dbinfo, "plm initialized");
- return 1;
-err_out:
- if (ppd->client_data_shm_id >= 0)
- shm_destroy(ppd->client_data_shm_id);
- if (ppd->client_mutex >= 0)
- mutex_destroy(ppd->client_mutex);
- if (ppd->server_mutex >= 0)
- mutex_destroy(ppd->server_mutex);
- free(ppd);
- return ret;
-}
+++ /dev/null
-/*
- * Copyright (C) 2004-2006 Andre Noll <maan@systemlinux.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, USA.
- */
-
-/** \file random_dbtool.c Simple database tool implementation. Feel free to modify. */
-
-#include <sys/time.h> /* gettimeofday */
-#include "server.cmdline.h"
-#include "server.h"
-#include "db.h"
-#include "error.h"
-#include "net.h"
-#include "string.h"
-
-static int com_random_info(int, int, char **);
-extern struct gengetopt_args_info conf;
-extern struct misc_meta_data *mmd;
-
-static unsigned int num_audio_files, audio_file_count;
-static char **audio_file_list;
-
-static int count_audio_files(__unused const char *dir, __unused const char *name)
-{
- num_audio_files++;
- return 1;
-}
-
-static int remember_file(const char *dir, const char *name)
-{
- if (audio_file_count >= num_audio_files)
- return -E_FILE_COUNT;
- audio_file_list[audio_file_count] = make_message("%s/%s", dir, name);
- audio_file_count++;
- return 1;
-}
-
-/* array of commands that are supported by this database tool */
-static struct server_command cmds[] = {
-{
-.name = "random_info",
-.handler = com_random_info,
-.perms = 0,
-.description = "about the random database tool",
-.synopsis = "random_info",
-.help =
-
-"Select a random file under the given directory"
-}, {
-.name = NULL,
-}
-};
-
-static int com_random_info(int fd, __unused int argc, __unused char *argv[])
-{
- return send_buffer(fd, "Don't use for huge directories as it is "
- "very inefficient in this case.\n");
-}
-
-/*
- * Load a list of all audio files into memory and chose num of them randomly.
- * Called by server to determine next audio file to be streamed.
- */
-static char **random_get_audio_file_list(unsigned int num)
-{
- int i, ret;
- unsigned int len;
- char **ret_list = NULL; /* what we are going to return */
-
- audio_file_list = NULL;
- num_audio_files = 0;
- /* first run, just count all audio files. dopey */
- ret = find_audio_files(conf.random_dbtool_dir_arg, count_audio_files);
- if (ret < 0)
- goto out;
- ret = -E_NOTHING_FOUND;
- if (!num_audio_files)
- goto out;
- /* yeah, that doesn't scale, also dopey */
- audio_file_list = para_malloc(num_audio_files * sizeof(char *));
- audio_file_count = 0;
- /* second run (hot dentry cache, hopefully), fill audio_file_list */
- ret = find_audio_files(conf.random_dbtool_dir_arg, remember_file);
- if (ret < 0)
- goto out;
- /* careful, files might got deleted underneath */
- num_audio_files = audio_file_count; /* can only decrease */
- len = MIN(num, num_audio_files);
- ret = -E_NOTHING_FOUND;
- if (!len) /* nothing found, return NULL */
- goto out;
- /* success, return NULL-terminated list */
- ret_list = para_calloc((len + 1) * sizeof(char *));
- for (i = 0; i < len; i++) { /* choose randomly */
- int r = (int) ((num_audio_files + 0.0) * (rand()
- / (RAND_MAX + 1.0)));
- ret_list[i] = para_strdup(audio_file_list[r]);
- }
-out:
- if (audio_file_list) {
- for (i = 0; i < num_audio_files; i++)
- free(audio_file_list[i]);
- free(audio_file_list);
- }
-// if (ret < 0)
-// PARA_ERROR_LOG("%s\n", PARA_STRERROR(-ret));
- return ret_list;
-}
-
-static void random_shutdown(void)
-{
- PARA_DEBUG_LOG("%s", "thanks for using another dbtool.\n");
-}
-
-/** random's (constant) database info text */
-#define DBINFO "dbinfo1:database info? You're kidding. I'm still dopey!\ndbinfo2:\ndbinfo3:\n"
-
-/**
- * the init function for the random database tool
- *
- * Init all function pointers of \a db, init the dbinfo text and seed the
- * PRNG.
- *
- * \sa struct dbtool, misc_meta_data::dbinfo, mysql.c
- */
-int random_dbtool_init(struct dbtool *db)
-{
- struct timeval now;
-
- PARA_INFO_LOG("%s", "registering random handlers ;)\n");
- sprintf(mmd->dbinfo, DBINFO);
- gettimeofday(&now, NULL);
- srand(now.tv_usec);
- db->cmd_list = cmds;
- db->get_audio_file_list = random_get_audio_file_list;
- db->shutdown = random_shutdown;
- return 1;
-}
--- /dev/null
+/*
+ * Copyright (C) 2004-2006 Andre Noll <maan@systemlinux.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, USA.
+ */
+
+/** \file random_selector.c An audio file selector which chooses files by random */
+
+#include <sys/time.h> /* gettimeofday */
+#include "server.cmdline.h"
+#include "server.h"
+#include "db.h"
+#include "error.h"
+#include "net.h"
+#include "string.h"
+
+static int com_random_info(int, int, char **);
+extern struct gengetopt_args_info conf;
+extern struct misc_meta_data *mmd;
+
+static unsigned int num_audio_files, audio_file_count;
+static char **audio_file_list;
+
+static int count_audio_files(__unused const char *dir, __unused const char *name)
+{
+ num_audio_files++;
+ return 1;
+}
+
+static int remember_file(const char *dir, const char *name)
+{
+ if (audio_file_count >= num_audio_files)
+ return -E_FILE_COUNT;
+ audio_file_list[audio_file_count] = make_message("%s/%s", dir, name);
+ audio_file_count++;
+ return 1;
+}
+
+/* array of commands that are supported by this selector */
+static struct server_command cmds[] = {
+{
+.name = "random_info",
+.handler = com_random_info,
+.perms = 0,
+.description = "about the random audio file selector",
+.synopsis = "random_info",
+.help =
+
+"Select a random file under the given directory"
+}, {
+.name = NULL,
+}
+};
+
+static int com_random_info(int fd, __unused int argc, __unused char *argv[])
+{
+ return send_buffer(fd, "Don't use for huge directories as it is "
+ "very inefficient in this case.\n");
+}
+
+/*
+ * Load a list of all audio files into memory and chose num of them randomly.
+ * Called by server to determine next audio file to be streamed.
+ */
+static char **random_get_audio_file_list(unsigned int num)
+{
+ int i, ret;
+ unsigned int len;
+ char **ret_list = NULL; /* what we are going to return */
+
+ audio_file_list = NULL;
+ num_audio_files = 0;
+ /* first run, just count all audio files. dopey */
+ ret = find_audio_files(conf.random_dir_arg, count_audio_files);
+ if (ret < 0)
+ goto out;
+ ret = -E_NOTHING_FOUND;
+ if (!num_audio_files)
+ goto out;
+ /* yeah, that doesn't scale, also dopey */
+ audio_file_list = para_malloc(num_audio_files * sizeof(char *));
+ audio_file_count = 0;
+ /* second run (hot dentry cache, hopefully), fill audio_file_list */
+ ret = find_audio_files(conf.random_dir_arg, remember_file);
+ if (ret < 0)
+ goto out;
+ /* careful, files might got deleted underneath */
+ num_audio_files = audio_file_count; /* can only decrease */
+ len = MIN(num, num_audio_files);
+ ret = -E_NOTHING_FOUND;
+ if (!len) /* nothing found, return NULL */
+ goto out;
+ /* success, return NULL-terminated list */
+ ret_list = para_calloc((len + 1) * sizeof(char *));
+ for (i = 0; i < len; i++) { /* choose randomly */
+ int r = (int) ((num_audio_files + 0.0) * (rand()
+ / (RAND_MAX + 1.0)));
+ ret_list[i] = para_strdup(audio_file_list[r]);
+ }
+out:
+ if (audio_file_list) {
+ for (i = 0; i < num_audio_files; i++)
+ free(audio_file_list[i]);
+ free(audio_file_list);
+ }
+// if (ret < 0)
+// PARA_ERROR_LOG("%s\n", PARA_STRERROR(-ret));
+ return ret_list;
+}
+
+static void random_shutdown(void)
+{
+}
+
+/**
+ * the init function for the random audio file selector
+ *
+ * Init all function pointers of \a db, init the info text and seed the
+ * PRNG.
+ *
+ * \sa struct audio_file_selector, misc_meta_data::selector_info, mysql.c
+ */
+int random_selector_init(struct audio_file_selector *db)
+{
+ struct timeval now;
+
+ PARA_INFO_LOG("%s", "registering random handlers ;)\n");
+ sprintf(mmd->selector_info, "dbinfo1:database info? You're kidding. "
+ "I'm still dopey!\ndbinfo2:\ndbinfo3:\n");
+ gettimeofday(&now, NULL);
+ srand(now.tv_usec);
+ db->cmd_list = cmds;
+ db->get_audio_file_list = random_get_audio_file_list;
+ db->shutdown = random_shutdown;
+ return 1;
+}
client_conf="$dir/client.conf"
audioc_conf="$dir/audioc.conf"
server=www.paraslash.org
-proj=paraslash-0.2.10
+proj=paraslash-0.2.11
df=$proj.tar.bz2 # download file
url=http://$server/versions/$df
kf="$dir/key.anonymous" # key file
key_url=http://$server/key.anonymous
socket="$dir/socket"
-receiver_opts="mp3:http:-i:$server:-p:8009"
+receiver_opts="mp3:http -i $server -p 8009"
audiod_log="$dir/audiod.log"
-audiod_opts="-FDdr $receiver_opts -L $audiod_log -s $socket"
+audiod_opts="-FDd -L $audiod_log -s $socket"
msg()
{
echo "`date`: $1"
msg "writing $audioc_conf"
echo "socket \"$socket\"" > "$audioc_conf"
(para_audioc term; killall para_audiod para_client) >> "$log" 2>&1
- msg "para_audiod $audiod_opts"
- para_audiod $audiod_opts -w "mp3:mpg123 -"
+ msg "para_audiod $audiod_opts -r '$receiver_opts'"
+ para_audiod $audiod_opts -r "$receiver_opts" -w "mp3:mpg123 -"
echo "hit return to start para_gui, hit ctrl+c to abort"
read
para_gui
s[SI_UPTIME].font = N_YELLOW;
s[SI_UPTIME].align = RIGHT;
- s[SI_DBTOOL].prefix = "dbtool: ";
- s[SI_DBTOOL].postfix = "";
- s[SI_DBTOOL].content = "no content yet";
- s[SI_DBTOOL].x = 35;
- s[SI_DBTOOL].y = 48;
- s[SI_DBTOOL].w = 35;
- s[SI_DBTOOL].h = FONT_HEIGHT;
- s[SI_DBTOOL].r = 0;
- s[SI_DBTOOL].g = 0;
- s[SI_DBTOOL].b = 0;
- s[SI_DBTOOL].font = N_YELLOW;
- s[SI_DBTOOL].align = LEFT;
+ s[SI_SELECTOR].prefix = "selector: ";
+ s[SI_SELECTOR].postfix = "";
+ s[SI_SELECTOR].content = "no content yet";
+ s[SI_SELECTOR].x = 35;
+ s[SI_SELECTOR].y = 48;
+ s[SI_SELECTOR].w = 35;
+ s[SI_SELECTOR].h = FONT_HEIGHT;
+ s[SI_SELECTOR].r = 0;
+ s[SI_SELECTOR].g = 0;
+ s[SI_SELECTOR].b = 0;
+ s[SI_SELECTOR].font = N_YELLOW;
+ s[SI_SELECTOR].align = LEFT;
s[SI_FORMAT].prefix = "Format: ";
s[SI_FORMAT].postfix = "";
/** \mainpage Paraslash API Reference
*
- * Good starting points for reading are probably \ref dbtool, \ref sender,
- * \ref receiver, \ref receiver_node, \ref filter, \ref filter_node.
+ * Good starting points for reading are probably \ref audio_file_selector,
+ * \ref sender, \ref receiver, \ref receiver_node, \ref filter, \ref
+ * filter_node.
*
*/
/** shut down non-authorized connections after that many seconds */
#define ALARM_TIMEOUT 10
-/* these are exported to afs/command/dbtool */
+/* these are exported to afs.c. command.c and to all selectors */
struct misc_meta_data *mmd;
/** the configuration of para_server
*
- * It also contains the options for all database tools and all supported
+ * It also contains the options for all audio file selectors and all supported
* senders.
*/
struct gengetopt_args_info conf;
extern void ortp_send_init(struct sender *);
extern struct audio_format afl[];
-/** the list of supported database tools */
-struct dbtool dblist[] = {
+/** the list of supported audio file selectors */
+struct audio_file_selector dblist[] = {
{
.name = "random",
- .init = random_dbtool_init,
+ .init = random_selector_init,
.update_audio_file = NULL,
},
{
- .name = "plm",
- .init = plm_dbtool_init,
+ .name = "playlist",
+ .init = playlist_selector_init,
.update_audio_file = NULL,
.pre_select = NULL,
.post_select = NULL,
#ifdef HAVE_MYSQL
{
.name = "mysql",
- .init = mysql_dbtool_init,
+ .init = mysql_selector_init,
.update_audio_file = NULL,
.pre_select = NULL,
.post_select = NULL,
goto err_out;
mmd_mutex = ret;
- mmd->dbt_num = 0;
+ mmd->selector_num = 0;
mmd->num_played = 0;
mmd->num_commands = 0;
mmd->events = 0;
}
}
-static void init_dbtool(void)
+static void init_selector(void)
{
int i, ret;
- mmd->dbt_change = -1; /* no change nec., set to new dbt num by com_cdt */
- if (!conf.dbtool_given)
+ mmd->selector_change = -1; /* no change nec., set to new num by com_chs */
+ if (!conf.selector_given)
goto random;
for (i = 0; dblist[i].name; i++) {
- if (strcmp(dblist[i].name, conf.dbtool_arg))
+ if (strcmp(dblist[i].name, conf.selector_arg))
continue;
- PARA_NOTICE_LOG("initializing %s database tool\n",
+ PARA_NOTICE_LOG("initializing %s audio file selector\n",
dblist[i].name);
ret = dblist[i].init(&dblist[i]);
if (ret < 0) {
PARA_WARNING_LOG("%s", PARA_STRERROR(-ret));
break;
}
- mmd->dbt_num = i;
+ mmd->selector_num = i;
return;
}
- PARA_WARNING_LOG("%s", "falling back to the random dbtool\n");
+ PARA_WARNING_LOG("%s", "falling back to the random selector\n");
random:
- mmd->dbt_num = 0;
+ mmd->selector_num = 0;
dblist[0].init(&dblist[0]); /* always successful */
}
/* become daemon */
if (conf.daemon_given)
daemon_init();
- init_dbtool();
+ init_selector();
PARA_NOTICE_LOG("%s", "initializing audio file sender\n");
/* audio file sender */
afs_init();
return sockfd;
}
-static void handle_dbt_change(void)
+static void change_selector(void)
{
- int ret, old = mmd->dbt_num, new = mmd->dbt_change;
+ int ret, old = mmd->selector_num, new = mmd->selector_change;
dblist[old].shutdown();
ret = dblist[new].init(&dblist[new]);
- mmd->dbt_change = -1; /* reset */
+ mmd->selector_change = -1; /* reset */
if (ret >= 0) {
- mmd->dbt_num = new;
+ mmd->selector_num = new;
return;
}
/* init failed */
- PARA_ERROR_LOG("%s -- switching to the random dbtool\n", PARA_STRERROR(-ret));
+ PARA_ERROR_LOG("%s -- switching to the random selector\n", PARA_STRERROR(-ret));
dblist[0].init(&dblist[0]);
- mmd->dbt_num = 0;
+ mmd->selector_num = 0;
}
/*
close_log(logfile); /* gets reopened if necessary by parse_config */
logfile = NULL;
parse_config(1); /* reopens log */
- mmd->dbt_change = mmd->dbt_num; /* do not change dbtool */
- handle_dbt_change(); /* force reloading dbtool */
+ mmd->selector_change = mmd->selector_num; /* do not change selector.. */
+ change_selector(); /* .. just reload */
}
static void status_refresh(void)
&max_fileno,
&rfds, &wfds);
}
- if (dblist[mmd->dbt_num].pre_select) {
- ret = dblist[mmd->dbt_num].pre_select(&rfds, &wfds);
+ if (dblist[mmd->selector_num].pre_select) {
+ ret = dblist[mmd->selector_num].pre_select(&rfds, &wfds);
max_fileno = MAX(max_fileno, ret);
}
mmd_unlock();
err = errno;
//PARA_DEBUG_LOG("%s: select returned %i\n", __func__, ret);
mmd_lock();
- if (mmd->dbt_change >= 0)
- handle_dbt_change();
- if (dblist[mmd->dbt_num].post_select)
- dblist[mmd->dbt_num].post_select(&rfds, &wfds);
+ if (mmd->selector_change >= 0)
+ change_selector();
+ if (dblist[mmd->selector_num].post_select)
+ dblist[mmd->selector_num].post_select(&rfds, &wfds);
if (ret < 0 && err == EINTR)
goto repeat;
if (ret < 0) {
case SIGTERM:
PARA_EMERG_LOG("terminating on signal %d\n", sig);
kill(0, SIGTERM);
- dblist[mmd->dbt_num].shutdown();
+ dblist[mmd->selector_num].shutdown();
mutex_destroy(mmd_mutex);
shm_detach(mmd);
shm_destroy(mmd_shm_id);
option "config_file" c "(default='~/.paraslash/server.conf'" string typestr="filename" no
option "user_list" - "(default='~/.paraslash/server.users')" string typestr="filename" no
-section "Options concerning the audio file sender"
+section "audio file sender"
option "autoplay" a "start playing on startup" flag off
option "announce_time" A "Delay betweeen announcing the stream and sending data" int typestr="milliseconds" default="300" no
-option "dbtool" D "(default=first available that works)" string typestr="name_of_dbtool" no
+option "selector" S "(default=random)" string typestr="name" no
-section "Mysql database tool options"
+section "mysql selector:"
option "mysql_host" - "mysql server" string default="localhost" no
option "mysql_port" - "where mysql is listening" int default="3306" no
option "mysql_user" - "default value: username from /etc/passwd" string no
option "mysql_audio_file_dir" - "dir to search for audio files (required)" string no
option "mysql_default_score" - "scoring rule to use if stream definition does not contain explicit score definition" string default="(LASTPLAYED() / 1440 - 1000 / (LASTPLAYED() + 1) - sqrt(NUMPLAYED()))" no
+section "random selector"
+option "random_dir" - "dir to search for audio files" string default="/home/music" no
-
-section "Random database tool options"
-option "random_dbtool_dir" - "dir to search for files to be streamed" string default="/home/music" no
-
-section "Http sender options"
+section "http sender"
option "http_port" - "tcp port for http streaming" int typestr="portnumber" default="8000" no
option "http_default_deny" - "deny connections from hosts which are not explicitly allowed" flag off
option "http_access" - "Add given host/network to access control list (whitelist if http_default_deny was given, blacklist otherwise) before opening the tcp port. This option can be given multiple times. Example: '192.168.0.0/24' whitelists/blacklists the 256 hosts 192.168.0.x" string typestr="a.b.c.d/n" no multiple
option "http_no_autostart" - "do not open tcp port on server startup" flag off
option "http_max_clients" - "maximal simultaneous connections, non-positive value means unlimited" int typestr="number" default="-1" no
-section "Dccp sender options"
+section "dccp sender"
option "dccp_port" - "port for http streaming" int typestr="portnumber" default="5001" no
-section "Ortp sender options"
+section "ortp sender"
option "ortp_target" - "Add given host/port to the list of targets. This option can be given multiple times. Example: '224.0.1.38:1500' instructs the ortp sender to send to udp port 1500 on host 224.0.1.38 (unassigned ip in the Local Network Control Block 224.0.0/24). This is useful for LAN-streaming." string typestr="a.b.c.d:p" no multiple
option "ortp_no_autostart" - "do not start to send automatically" flag off
option "ortp_default_port" - "default udp port if not specified" int typestr="portnumber" default="1500" no
#include "para.h"
-/** size of the dbinfo and audio_file info strings of struct misc_meta_data */
+/** size of the selector_info and audio_file info strings of struct misc_meta_data */
#define MMD_INFO_SIZE 16384
/**
unsigned int active_connections;
/** the process id of para_server */
pid_t server_pid;
-/** a string that gets filled in by the current database tool */
- char dbinfo[MMD_INFO_SIZE];
-/** the number if the current database tool */
- int dbt_num;
-/** commands set this to non-zero to request a database tool change */
- int dbt_change;
+/** a string that gets filled in by the current audio file selector */
+ char selector_info[MMD_INFO_SIZE];
+/** the number if the current audio file selector */
+ int selector_num;
+/** commands set this to non-zero to change the current selector */
+ int selector_change;
/** used by the sender command */
struct sender_command_data sender_cmd_data;
};
#define VAL_2_SL_VAL(v) (v) * (SLIDER_RATIO - 1.0) / SLIDER_RATIO
#define SL_VAL_2_VAL(v) (v) * SLIDER_RATIO / (SLIDER_RATIO - 1.0)
#define VAL_2_SCORE(v) (v) > 0.5? 1 / (1.03 - (v)) / (1.03 - (v)) :\
- - 1 / ((v) + 0.03) / ((v) + 0.03)
+ - 1 / ((v) + 0.03) / ((v) + 0.03)
#define RGB(R,G,B) (((R)<<12) + ((G)<<6) + (B))
#define NUMPLAYED_FORMULA(v)\
10 * (v) + (v) / (1 - (v) * (1 - EPSILON))
-
static int argc;
static char **argv;
char *streamname = NULL;
static Zmw_Float_0_1 *slider_vals, lastplayed_val, numplayed_val;
-#if 0
-static void boxed_text(const char *text, int i)
-{
- ZMW(zmw_decorator(Zmw_Decorator_Border_Embossed))
- {
- if (i == 2)
- {
- zmw_horizontal_expand(Zmw_False) ;
- zmw_vertical_expand(Zmw_False) ;
- }
- zmw_label(text) ;
- }
-}
-#endif
-
void para_log(int ll, char* fmt,...) /* no logging */
{
}
[SI_CURRENT_TIME] = "current_time",
[SI_AUDIOD_UPTIME] = "audiod_uptime",
- [SI_DBTOOL] = "dbtool"
+ [SI_SELECTOR] = "dbtool"
};
#define FOR_EACH_STAT_ITEM(i) for (i = 0; i < NUM_STAT_ITEMS; i++)
--- /dev/null
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.2.2 (GNU/Linux)
+
+iD8DBQBEEuu2Wto1QDEAkw8RAoZ9AJ9WqLLqMOmyF7fi417dzI50uzFPKACfTrtx
+kka6qNGfM2jUOJhaMa7ELQ4=
+=uvOA
+-----END PGP SIGNATURE-----
<a href="INSTALL.html">INSTALL</a>
for installation notes, and
<a href="README.mysql.html">README.mysql</a>
-for instructions on how to use the mysql database tool
+for instructions on how to use the mysql audio file selector
shipped with paraslash.</p>
<p>
The various commands of para_server and para_audiod are explained in
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=iso-8859-1'>
+ <title>Paraslash</title>
+ <LINK href="../../para.css" REL="stylesheet" TYPE="text/css">
+ <link rel="shortcut icon" href="../../paraslash.ico">
+</head>
+<body>
+ <basefont face="lucida, helvetica, arial" size="3">
+ <table border="0" cellpadding="10" cellspacing="0">
+ <tr>
+ <td>
+ <a href="../..//"><IMG SRC="../../paraslash.png" alt="paraslash" border="0"></a><BR>
+ </td>
+ <td>
+ <h3>Paraslash: Play, archive, rate and stream
+ large audio sets happily</h3>
+
+ A set of tools for doing just what its name
+ suggests.
+ </td>
+ </tr>
+ <tr>
+ <td valign="TOP">
+ <br><a href="../../index.html">Home</a>
+ <br><a href="../../NEWS.html">News</a>
+ <br><a href="../../FEATURES.html">Features</a>
+ <br><a href="../../download.html">Download</a>
+ <br><a href="../../screenshots.html">Screenshots</a>
+ <br><a href="../../demo.html">Live Demo</a>
+ <br><a href="/cgi-bin/gitweb.cgi?p=.git;a=shortlog">Changes</a>
+ <br><a href="../../documentation.html">Documentation</a>
+ <br><a href="../../license.html">License</a>
+ <br><a href="../../contact.html">Contact</a>
+ <br><a href="../../CREDITS.html">Credits</a>
+ </td>
+ <td Valign="TOP">
+ <hr>
<h3>Events</h3>
<ul>
+ <li>2006-03-11: <a href="versions/paraslash-0.2.11.tar.bz2">paraslash-0.2.11</a>
+ <a href="versions/paraslash-0.2.11.tar.bz2.asc">(sig)</a>
+ atomic duality"
+ </li>
<li>2006-02-22: <a href="/cgi-bin/gitweb.cgi?p=.git;a=shortlog">browsable changelog</a> (gitweb)</li>
<li>2006-02-17: <a href="versions/paraslash-0.2.10.tar.bz2">paraslash-0.2.10</a>
<a href="versions/paraslash-0.2.10.tar.bz2.asc">(sig)</a> "cyclic attractor"</li>