initial git commit
authorAndre <maan@p133.(none)>
Mon, 20 Feb 2006 06:03:36 +0000 (07:03 +0100)
committerAndre <maan@p133.(none)>
Mon, 20 Feb 2006 06:03:36 +0000 (07:03 +0100)
Let's try if this works out.

172 files changed:
.changelog_before_cvs [new file with mode: 0644]
1.0 [new file with mode: 0644]
COPYING [new file with mode: 0644]
CREDITS [new file with mode: 0644]
Doxyfile [new file with mode: 0644]
FEATURES [new file with mode: 0644]
GPL [new file with mode: 0644]
INSTALL [new file with mode: 0644]
Makefile.in [new file with mode: 0644]
NEWS [new file with mode: 0644]
PUBLIC_KEY [new file with mode: 0644]
README [new file with mode: 0644]
README.mysql [new file with mode: 0644]
SFont.c [new file with mode: 0644]
SFont.h [new file with mode: 0644]
afs.c [new file with mode: 0644]
afs.h [new file with mode: 0644]
audioc.c [new file with mode: 0644]
audioc.ggo [new file with mode: 0644]
audiod.c [new file with mode: 0644]
audiod.ggo [new file with mode: 0644]
audiod.h [new file with mode: 0644]
autogen.sh [new file with mode: 0755]
bash_completion [new file with mode: 0644]
client.c [new file with mode: 0644]
client.ggo [new file with mode: 0644]
close_on_fork.c [new file with mode: 0644]
close_on_fork.h [new file with mode: 0644]
command.c [new file with mode: 0644]
compress.c [new file with mode: 0644]
compress_filter.ggo [new file with mode: 0644]
configure.ac [new file with mode: 0644]
crypt.c [new file with mode: 0644]
crypt.h [new file with mode: 0644]
daemon.c [new file with mode: 0644]
daemon.h [new file with mode: 0644]
db.c [new file with mode: 0644]
db.h [new file with mode: 0644]
dbadm.c [new file with mode: 0644]
dopey.c [new file with mode: 0644]
error.h [new file with mode: 0644]
exec.c [new file with mode: 0644]
fade.c [new file with mode: 0644]
fade.ggo [new file with mode: 0644]
filter.c [new file with mode: 0644]
filter.ggo [new file with mode: 0644]
filter.h [new file with mode: 0644]
filter_chain.c [new file with mode: 0644]
fonts/24P_Arial_Blue.png [new file with mode: 0644]
fonts/24P_Arial_Metallic_Yellow.png [new file with mode: 0644]
fonts/24P_Arial_NeonBlue.png [new file with mode: 0644]
fonts/24P_Arial_NeonYellow.png [new file with mode: 0644]
fonts/24P_Copperplate_Blue.png [new file with mode: 0644]
gcc-compat.h [new file with mode: 0644]
grab_client.c [new file with mode: 0644]
grab_client.ggo [new file with mode: 0644]
grab_client.h [new file with mode: 0644]
gui.c [new file with mode: 0644]
gui.conf.sample [new file with mode: 0644]
gui.ggo [new file with mode: 0644]
gui_common.c [new file with mode: 0644]
gui_theme.c [new file with mode: 0644]
http.h [new file with mode: 0644]
http_recv.c [new file with mode: 0644]
http_recv.ggo [new file with mode: 0644]
http_send.c [new file with mode: 0644]
index.html [new file with mode: 0644]
install-sh [new file with mode: 0755]
key.anonymous [new file with mode: 0644]
krell.c [new file with mode: 0644]
list.h [new file with mode: 0644]
mp3.c [new file with mode: 0644]
mp3dec.c [new file with mode: 0644]
mysql.c [new file with mode: 0644]
net.c [new file with mode: 0644]
net.h [new file with mode: 0644]
ogg.c [new file with mode: 0644]
oggdec.c [new file with mode: 0644]
oggdec_filter.ggo [new file with mode: 0644]
ortp.h [new file with mode: 0644]
ortp_recv.c [new file with mode: 0644]
ortp_recv.ggo [new file with mode: 0644]
ortp_send.c [new file with mode: 0644]
para.h [new file with mode: 0644]
pics/paraslash/default.jpg [new file with mode: 0644]
pics/screenshots/gui-2004-07-11.png [new file with mode: 0644]
pics/screenshots/gui-2004-09-02.png [new file with mode: 0644]
pics/screenshots/gui-2005-11-12.png [new file with mode: 0644]
pics/screenshots/gui-old.png [new file with mode: 0644]
pics/screenshots/loglevel1-2004-07-28.txt [new file with mode: 0644]
pics/screenshots/loglevel1-2005-03-23.txt [new file with mode: 0644]
pics/screenshots/para_audiod-startup.txt [new file with mode: 0644]
pics/screenshots/para_krell-2005-02.png [new file with mode: 0644]
pics/screenshots/para_server-startup.txt [new file with mode: 0644]
pics/screenshots/para_slider-2004-12.png [new file with mode: 0644]
pics/screenshots/sdl_gui.jpg [new file with mode: 0644]
pics/web/paraslash.ico [new file with mode: 0644]
pics/web/paraslash.png [new file with mode: 0644]
play.c [new file with mode: 0644]
play.ggo [new file with mode: 0644]
rc4.h [new file with mode: 0644]
recv.c [new file with mode: 0644]
recv.ggo [new file with mode: 0644]
recv.h [new file with mode: 0644]
recv_common.c [new file with mode: 0644]
ringbuffer.c [new file with mode: 0644]
ringbuffer.h [new file with mode: 0644]
scripts/demo-script [new file with mode: 0755]
sdl_gui.c [new file with mode: 0644]
sdl_gui.ggo [new file with mode: 0644]
send.h [new file with mode: 0644]
server.c [new file with mode: 0644]
server.ggo [new file with mode: 0644]
server.h [new file with mode: 0644]
signal.c [new file with mode: 0644]
skencil/overview.sk [new file with mode: 0644]
slider.c [new file with mode: 0644]
stat.c [new file with mode: 0644]
string.c [new file with mode: 0644]
string.h [new file with mode: 0644]
time.c [new file with mode: 0644]
versions/paraslash-0.0.99.tgz [new file with mode: 0644]
versions/paraslash-0.0.99.tgz.asc [new file with mode: 0644]
versions/paraslash-0.1.0.tgz [new file with mode: 0644]
versions/paraslash-0.1.0.tgz.asc [new file with mode: 0644]
versions/paraslash-0.1.1.tgz [new file with mode: 0644]
versions/paraslash-0.1.1.tgz.asc [new file with mode: 0644]
versions/paraslash-0.1.2.tgz [new file with mode: 0644]
versions/paraslash-0.1.2.tgz.asc [new file with mode: 0644]
versions/paraslash-0.1.3.tgz [new file with mode: 0644]
versions/paraslash-0.1.3.tgz.asc [new file with mode: 0644]
versions/paraslash-0.1.4.tgz [new file with mode: 0644]
versions/paraslash-0.1.4.tgz.asc [new file with mode: 0644]
versions/paraslash-0.1.5.tgz [new file with mode: 0644]
versions/paraslash-0.1.5.tgz.asc [new file with mode: 0644]
versions/paraslash-0.1.6.tgz [new file with mode: 0644]
versions/paraslash-0.1.6.tgz.asc [new file with mode: 0644]
versions/paraslash-0.1.7.tgz [new file with mode: 0644]
versions/paraslash-0.1.7.tgz.asc [new file with mode: 0644]
versions/paraslash-0.2.0.tar.bz2 [new file with mode: 0644]
versions/paraslash-0.2.0.tar.bz2.asc [new file with mode: 0644]
versions/paraslash-0.2.1.tar.bz2 [new file with mode: 0644]
versions/paraslash-0.2.1.tar.bz2.asc [new file with mode: 0644]
versions/paraslash-0.2.10.tar.bz2 [new file with mode: 0644]
versions/paraslash-0.2.10.tar.bz2.asc [new file with mode: 0644]
versions/paraslash-0.2.2.tar.bz2 [new file with mode: 0644]
versions/paraslash-0.2.2.tar.bz2.asc [new file with mode: 0644]
versions/paraslash-0.2.3.tar.bz2 [new file with mode: 0644]
versions/paraslash-0.2.3.tar.bz2.asc [new file with mode: 0644]
versions/paraslash-0.2.4.tar.bz2 [new file with mode: 0644]
versions/paraslash-0.2.4.tar.bz2.asc [new file with mode: 0644]
versions/paraslash-0.2.5.tar.bz2 [new file with mode: 0644]
versions/paraslash-0.2.5.tar.bz2.asc [new file with mode: 0644]
versions/paraslash-0.2.6.tar.bz2 [new file with mode: 0644]
versions/paraslash-0.2.6.tar.bz2.asc [new file with mode: 0644]
versions/paraslash-0.2.7.tar.bz2 [new file with mode: 0644]
versions/paraslash-0.2.7.tar.bz2.asc [new file with mode: 0644]
versions/paraslash-0.2.8.tar.bz2 [new file with mode: 0644]
versions/paraslash-0.2.8.tar.bz2.asc [new file with mode: 0644]
versions/paraslash-0.2.9.tar.bz2 [new file with mode: 0644]
versions/paraslash-0.2.9.tar.bz2.asc [new file with mode: 0644]
wav.c [new file with mode: 0644]
web/contact.in.html [new file with mode: 0644]
web/demo.in.html [new file with mode: 0644]
web/documentation.in.html [new file with mode: 0644]
web/download.in.html [new file with mode: 0644]
web/footer.html [new file with mode: 0644]
web/header.html [new file with mode: 0644]
web/index.in.html [new file with mode: 0644]
web/license.in.html [new file with mode: 0644]
web/para.css [new file with mode: 0644]
web/screenshots.in.html [new file with mode: 0644]

diff --git a/.changelog_before_cvs b/.changelog_before_cvs
new file mode 100644 (file)
index 0000000..7f95255
--- /dev/null
@@ -0,0 +1,378 @@
+This is the old Changelog, before the icc project switched to cvs.
+
+dbtool (sa): fix SEGFAULT when some attributes are NULL
+icc_dbtool.template: add help command
+Makefile: Check for mp3info
+dbtool.c: Handle files with «'» properly
+
+****** Version 91 (Sat Nov 30 13:52:37 MET 2002) *******
+Makefile: include icc_dbtool.conf.sample
+icebear: Remove unneeded signal handlers
+icc_dbtool.c Add documentation to upd command
+Documentation update
+dbtool.c: Add config file functionality
+icc_dbtool.c: Add upd command
+command.c: Bug: cs prints current stream in err msg
+zombies, zombies
+many fixes all over the place
+dbtool.c: new command: vrfy (verify entries in database)
+dbtool.c: implement clean command
+icc_dbtool: implement skip
+dont use pointers to malloced mem in shared area (causes SEGFAULT)
+icebear: SEGFAULT when cs was called after server had got HUP. Nasty bug
+icc_bash_completion: adapt to new syntax
+command.c: nuke drop_ss and new_ss
+dbtool.c: Add da (drop attribut) command (replaces old drop_ss)
+dbtool.c: Add na (new attribut) command (replaces old new_ss)
+Makefile: Changelog contains entries not yet done
+
+****** Version 90 (Wed Nov 13 02:05:34 MET 2002) *******
+icebear: kill unneeded signal handlers
+icebear: call info only if it is a valid command
+server: code cleanup
+command.c uptime: print #connections and #commands
+server: count #connections and #commands
+server/icebear/command: count/display number of songs already played
+icc.h: cleanup
+command.c: nuke ms command
+dbtool.c: add summary command
+dbtool.c: new command: sa, replaces old ss (set subset) command
+dbtool.c: new command: streams
+server: renew command list on SIGHUP
+icebear: call us only if it is a valid command
+command.c: sort command list alphabetically
+server: Nicify logging
+command.c: New command: perms
+command.c: Rewrite command handlers to use linked list
+command.c: introdule linked list of commands
+
+****** Version 89 (Sat Nov  9 17:27:32 MET 2002) *******
+command.c: Major cleanup
+
+****** Version 88 (Sat Nov  9 01:34:14 MET 2002) *******
+icebear: New command: cs
+init_icebear put default_stream to freshly allocated shared mem
+server: pass default_stream to init_icebear
+server: new configfile option: default_stream
+command.c help: call icc_dbtool help
+
+****** Version 87 (Fri Nov  8 23:03:03 MET 2002) *******
+Makefile: new target icc_dbtool
+icc_dbtool: Switch to mysql C API
+
+****** Version 86 (Thu Oct 17 22:38:37 MEST 2002) *******
+New file: README.dbtool
+client/server: use OPENSSL define of config.h instead of hard coded path
+client/server: include config.h
+README: add icecast to requirements
+
+****** Version 85 (Thu Oct 10 22:56:29 MEST 2002) *******
+Makefile: Don't include tdldb in tgz
+Makefile: New target: distclean (clean no longer removes Changelog, TODO and version.h)
+dbtool ms: print each dot only once (reduces size of ps from 171kb to 22kb)
+
+****** Version 84 (Fri Oct  4 15:01:54 MEST 2002) *******
+icc_server.conf.sample/icecast.conf.sample: Use same passwd
+Makefile: include icecast.conf.sample in tarball
+Makefile: mkdir ~/public_html
+
+****** Version 83 (Wed Sep 25 01:59:06 MEST 2002) *******
+Makefile tgz: php crap got messed up
+
+****** Version 82 (Wed Sep 25 01:56:55 MEST 2002) *******
+server: Reread configuration on SIGHUP
+Makefile: New Changelog format
+Makefile tgz: create archive with leading directory icc-$version
+
+************* Version 81 *************
+server update_str: fix bug: uptime string messed up
+README: add description of icc_bash_completion
+INSTALL: Update, extentions and nicifying
+icc_bash_completion: adapt to new layout of «icc help«
+
+************* Version 80 *************
+server: print uptime in welcome message
+server: new functions: uptime,uptime_str
+command.c: new commands: uptime,version
+icebear: exec_cmd: test if stopped for ff/jmp/next. Fixes Segfault when eg «next» was called with no file opened yet
+
+************* Version 79 *************
+server: implement remaining part of configuration part
+server: implement basic configuration file functionality
+
+************* Version 78 *************
+Makefile: install icc_client.conf.sample and also include it in tgz
+new file: icc_client.conf.sample
+client: actually print version and info if loglevel is at least info
+client: new function: print_version
+client: read_config: change verbose setting to loglevel
+client: new option -l (loglevel) obsoletes -v (verbose)
+client: Fix «if(verbose==TRUE)» madness, use loglevels instead
+client: write log function similar to server's log function
+README: Update icc_client description: Delete sentence on telnet
+
+************* Version 77 *************
+client: take localhost as default host, not p133
+client: new option -c (specify config file)
+client: implement reading of config file
+
+************* Version 76 *************
+server sigchld_handler: Add exit status also as numerical value
+command.c: Add description for drop_ss, fut, us
+
+************* Version 75 *************
+icebear: fix segfault on nomore (double close mp3_stream)
+icebear: nm->nomore
+command.c: add nomore command (got forgotten)
+client: catch SIGCHLD
+client: introduce internal variable debug to toggle debug before command line argumants are parsed (for verbose switch)
+client: Nicify verbose output
+client: Fix command line parsing
+
+************* Version 74 *************
+Makefile tgz: use prefix icc-0.0.
+Makefile tgz: bug: forget to read VERSION
+Makefile: Add .tdldb to sources
+
+************* Version 73 *************
+client: new option -V (version)
+server: new option -V (version)
+Makefile: new target: tgz (implies version)
+Makefile: fix version numbering
+Makefile: include INSTALL in sources
+Makefile: Use -p Option for tar
+
+************* Version 72 *************
+icc_server: print info on who we are serving to argv[0]
+icebear: print status to argv[0]
+command.c: Add missing descriptions to all internal commands
+dbtool.template: Use find instead of locate
+server: send proper error message if client sent unkown command
+
+************* Version 71 *************
+client: Added help option -h
+command.c help: Nicify output. Include needed permissions.
+command.c: Add description for stat, sb and term
+Makefile: Add target dbtool_install
+Makefile install: don't install dbtool
+server/client: print version number
+icc.h include version.h
+Makefile: added target version.h
+added INSTALL
+README: Remove Warning message concerning lack of authentification
+Makefile: implement PREFIX
+icebear: typo in jmp command
+icc_dbtool.template us: Don't print warning
+Makefile install: Install also icc_dbtool.template
+
+************* Version 70 *************
+
+************* Version 69 *************
+icc_dbtool.template: Change fut so that it does not need bogosort anymore
+
+************* Version 68 *************
+Makefile: include icc_dbtool.template in tgz package
+server: new command line options: P, i, ip
+server: kill global variable conn
+command.c parse_cmd: Fix SEGV when in interactive mode and no option given (reported by Christof Müller)
+Makefile: all: dont make TODO and Changelog
+icc.client: remove passwd
+icc.h remove maan
+
+************* Version 67 *************
+icebear: delete option for id3 comment in mp3info call
+icebear: Fix bug: pollret returns strange events
+icebear: fix division by 0 bug when length of file is zero seconds
+
+************* Version 66 *************
+
+************* Version 65 *************
+command.c: perror -> log
+command.c: new fuction: check_permissions
+command.c: insert dummy at the beginning of command list to let start command numbers by 1 instead of 0
+
+************* Version 64 *************
+server: add string strerror(errno) to log file entries
+command.c: make parse_command only return command number and call handle_cmd from main
+server: print log msg _before_ exit(1) if send fails
+server: exit(1) if send fails
+
+************* Version 63 *************
+command.c: code cleanup
+icc_server: implement option -c (specify config file)
+client: fix SEGFAULT when command line ends with invalid option
+client: add option -k (secret key file)
+
+************* Version 62 *************
+implement permission check for all commands
+
+************* Version 61 *************
+implement basic configuration file functionality
+icc_server/client: implement rsa authentification via openssl
+
+************* Version 60 *************
+command.c: add help text for jmp and ff commands
+command.c: help: print command handler and needed privileges
+icebear: minor code cleanups
+icebear: Nicify log output
+icebear: split poll_cmd_listener to allow blocking reads => no more sleep(1)
+
+************* Version 59 *************
+icebear: Code documentation
+icebear: move shout init/shoutdown to exec_cmd. This might fix pause command.
+
+************* Version 58 *************
+icebear: increase number of allowed invalid files before giving up to 100
+dbtool clean: Handle files with «'» properly
+
+************* Version 57 *************
+dbtool: add summary command
+icc_process_form.php: change strcmp(a,b)=0 to not strcmp(a,b)
+
+************* Version 56 *************
+
+************* Version 55 *************
+kill id3 code
+dbtool: change info and fut to avoid Lost connection to MySQL server during query
+icebear: It dies if there no valid songs. Stop and lurk for play instead
+icebear: code cleanup
+
+************* Version 54 *************
+icebear/icc.h: use long unsigned int to avoid overflow in status bar
+
+************* Version 53 *************
+server: BUG in help: dont check args if args=NULL
+command.c help: implement »help command«
+README: add LICENSE
+README: include description to php scripts
+server: kill guics command
+server: kill log command
+dbtool: kill pw
+dbtool: add local for all local vars
+
+************* Version 52 *************
+command.c, icc.h: cleanup declaration of command struct
+dbtool us: add option to specify update time
+add bash completion for icc_client
+
+************* Version 51 *************
+kill spl command
+dbtool: add ls command
+icc_server: add ls command
+icc_server: kill cd command
+
+************* Version 50 *************
+php/icc_form, php/icc_process_form: fix several bugs
+
+************* Version 49 *************
+add background gradient image
+
+************* Version 48 *************
+write mp3 search form icc_form.php
+icebear: log seconds with 2 digits
+icc_gui: Add sleep(2) before each reconnect
+php: delete unneeded pics
+
+************* Version 47 *************
+icc_server: add time and date to log
+
+************* Version 46 *************
+php/icc_dbgui: fix off by one bug
+dbtool: clean reports files as deleted which are still there
+remove wtc.jpg
+
+*** Version 45 ***
+improve php scripts
+
+*** Version 44 ***
+add php scripts for apache/mysql
+
+*** Version 43 ***
+icc.h changed #define ERR to #define ERROR
+
+*** Version 42 ***
+gui: make it survive window resize
+
+*** Version 41 ***
+Add short description of all icc tools to README
+
+*** Version 40 ***
+dbtool fut: replace use of ancient anplay variable by aprropriate one
+dbtool ss: make it work again when args _are_ given
+server: Add -p <port> option
+client: catch SIGINT
+
+*** Version 39 ***
+dbtool: ps does not work if no arg is given
+dbtool: ss does not work if no arg is given
+server: log GPL banner (loglevel INFO)
+client: Add GPL banner in welcome message
+dbtool last: print full filenames
+dbtool info: print name if no argument was given
+
+*** Version 38 ***
+icebear: make jmp,ff,next,pause,play,stop,nm actually respond via new pipe
+create new pipe icebear->server for responses to icebear commands
+
+*** Version 37 ***
+dbtool cs: print info if no argument is given
+
+*** Version 36 ***
+icebear: kill getenv code
+icebear: rewrite startup code to prevent icebear from playing on startup
+
+*** Version 35 ***
+icebear: nicify log messages
+icebear: make pause, play, stop work in all possible cases
+
+*** Version 34 ***
+Makefile version: include COPYRIGHT, GPL and README files in tarball
+Add COPYRIGHT and GPL files
+*.c *.h dbtool: Add GPL headers
+inplement option -L logfile
+client: Fix bug: commands with options don't work
+
+*** Version 33 ***
+Makefile: only put completed todos into Changelog
+server: implement Daemon mode
+icc_client: Dont send empty lines
+
+*** Version 32 ***
+command: Fix nasty bug which caused sending 2 nullbytes instead of only one
+dbtool info: print info on current song if no argument was given
+
+*** Version 31 ***
+dbtool: Another silly bug in info which prevented dir to be printed
+client: Fix silly bug caused it to send wrong number of bytes
+icc_server: implemented term command
+Makefile: strip away useless tdl info
+icc_dbtool: Changed Warning message for commands not yet implemented
+icc_server: Added help command
+icc_client: add readline support
+icc_dbtool fut: Fix bug when there is exactly one new song
+db_tool: implement skip
+db_tool: implement ss
+db_tool: implement clean
+db_tool: implement upd
+dbtool: implemented pw
+move icc ms to dbtool
+icc_shell: ms
+icc_server/dbtool: new command hist
+dbtool: implement last
+created icc_shell
+make icebear use icc_dbtool instead of mp3
+write db_tool basics (fut, ass, ps, info, us)
+Makefile improvements
+icc_client: Add verbose flag -v
+Adjust loglevels and make logging nicer
+icc_client: properly handle command line options
+icc_server: Dont start playing on startup
+take output of info command as misc info
+icc_client: treat nr_options correctly
+log messages cleanup
+log proper exit status (WIFSIGNALED...)
+icebear: stop actually restarts icebear
+icc_client: interactive mode
+icc_server: on errors send messages clients
+
+
diff --git a/1.0 b/1.0
new file mode 100644 (file)
index 0000000..f1c2f4f
--- /dev/null
+++ b/1.0
@@ -0,0 +1,2 @@
+- lyrics
+- gui: bot window scrollable
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..0847591
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,16 @@
+Copyright (C) 1997-2005 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.
+
diff --git a/CREDITS b/CREDITS
new file mode 100644 (file)
index 0000000..981a99c
--- /dev/null
+++ b/CREDITS
@@ -0,0 +1,41 @@
+Credits
+=======
+
+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.
+
+These people include:
+
+Karl Bartel <karlb@gmx.net> (SFont)
+
+Lorenzo Bettini <bettini@dsi.unifi.it> (gengetopt)
+
+Ricardo Cerqueira <rmc@rccn.net> (mp3info)
+
+Thierry Excoffier <exco@ligim.univ-lyon1.fr(zmw)> (libzmw)
+
+Thomas Forell (bug reports)
+
+Silke Klassert (bug reports)
+
+Jaroslav Kysela <perex@suse.cz> (aplay)
+
+Robert Leslie (libmad)
+
+Simon Morlat <simon.morlat@linphone.org> (ortp)
+
+Christof Müller (bug reports)
+
+M. Hari Nezumi (AudioCompress) <magenta@trikuare.cx>
+
+Manuel Odendahl <manuel-poc@bl0rg.net> (poc)
+
+Christian Reißmann (design)
+
+Michael Smith <msmith@xiph.org> (vcut)
+
+Cedric Tefft <cedric@earthling.net> (mp3info)
+
+Linus Torvalds <torvalds@osdl.org> (for giving us one hell of an
+operating system [quote taken from README.linux for DOOM v1.666])
diff --git a/Doxyfile b/Doxyfile
new file mode 100644 (file)
index 0000000..090e120
--- /dev/null
+++ b/Doxyfile
@@ -0,0 +1,1232 @@
+# Doxyfile 1.4.6
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+#       TAG = value [value, ...]
+# For lists items can also be appended using:
+#       TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded 
+# by quotes) that should identify the project.
+
+PROJECT_NAME           = paraslash
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. 
+# This could be handy for archiving the generated documentation or 
+# if some version control system is used.
+
+PROJECT_NUMBER         = 
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) 
+# base path where the generated documentation will be put. 
+# If a relative path is entered, it will be relative to the location 
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = web/sync/doxygen
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 
+# 4096 sub-directories (in 2 levels) under the output directory of each output 
+# format and will distribute the generated files over these directories. 
+# Enabling this option can be useful when feeding doxygen a huge amount of 
+# source files, where putting all generated files in the same directory would 
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all 
+# documentation generated by doxygen is written. Doxygen will use this 
+# information to generate all constant output in the proper language. 
+# The default language is English, other supported languages are: 
+# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, 
+# Dutch, Finnish, French, German, Greek, Hungarian, Italian, Japanese, 
+# Japanese-en (Japanese with English messages), Korean, Korean-en, Norwegian, 
+# Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, 
+# Swedish, and Ukrainian.
+
+OUTPUT_LANGUAGE        = English
+
+# This tag can be used to specify the encoding used in the generated output. 
+# The encoding is not always determined by the language that is chosen, 
+# but also whether or not the output is meant for Windows or non-Windows users. 
+# In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES 
+# forces the Windows encoding (this is the default for the Windows binary), 
+# whereas setting the tag to NO uses a Unix-style encoding (the default for 
+# all platforms other than Windows).
+
+USE_WINDOWS_ENCODING   = NO
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will 
+# include brief member descriptions after the members that are listed in 
+# the file and class documentation (similar to JavaDoc). 
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend 
+# the brief description of a member or function before the detailed description. 
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the 
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator 
+# that is used to form the text in various listings. Each string 
+# in this list, if found as the leading text of the brief description, will be 
+# stripped from the text and the result after processing the whole list, is 
+# used as the annotated text. Otherwise, the brief description is used as-is. 
+# If left blank, the following values are used ("$name" is automatically 
+# replaced with the name of the entity): "The $name class" "The $name widget" 
+# "The $name file" "is" "provides" "specifies" "contains" 
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF       = 
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then 
+# Doxygen will generate a detailed section even if there is only a brief 
+# description.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all 
+# inherited members of a class in the documentation of that class as if those 
+# members were ordinary class members. Constructors, destructors and assignment 
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full 
+# path before files name in the file list and in the header files. If set 
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = YES
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag 
+# can be used to strip a user-defined part of the path. Stripping is 
+# only done if one of the specified strings matches the left-hand part of 
+# the path. The tag can be used to show relative paths in the file list. 
+# If left blank the directory from which doxygen is run is used as the 
+# path to strip.
+
+STRIP_FROM_PATH        = 
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of 
+# the path mentioned in the documentation of a class, which tells 
+# the reader which header file to include in order to use a class. 
+# If left blank only the name of the header file containing the class 
+# definition is used. Otherwise one should specify the include paths that 
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH    = 
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter 
+# (but less readable) file names. This can be useful is your file systems 
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen 
+# will interpret the first line (until the first dot) of a JavaDoc-style 
+# comment as the brief description. If set to NO, the JavaDoc 
+# comments will behave just like the Qt-style comments (thus requiring an 
+# explicit @brief command for a brief description.
+
+JAVADOC_AUTOBRIEF      = YES
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen 
+# treat a multi-line C++ special comment block (i.e. a block of //! or /// 
+# comments) as a brief description. This used to be the default behaviour. 
+# The new default is to treat a multi-line C++ comment block as a detailed 
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the DETAILS_AT_TOP tag is set to YES then Doxygen 
+# will output the detailed description near the top, like JavaDoc.
+# If set to NO, the detailed description appears after the member 
+# documentation.
+
+DETAILS_AT_TOP         = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented 
+# member inherits the documentation from any documented member that it 
+# re-implements.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce 
+# a new page for each member. If set to NO, the documentation of a member will 
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. 
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 8
+
+# This tag can be used to specify a number of aliases that acts 
+# as commands in the documentation. An alias has the form "name=value". 
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to 
+# put the command \sideeffect (or @sideeffect) in the documentation, which 
+# will result in a user-defined paragraph with heading "Side Effects:". 
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                = 
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C 
+# sources only. Doxygen will then generate output that is more tailored for C. 
+# For instance, some of the names that are used will be different. The list 
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java 
+# sources only. Doxygen will then generate output that is more tailored for Java. 
+# For instance, namespaces will be presented as packages, qualified scopes 
+# will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want to 
+# include (a tag file for) the STL sources as input, then you should 
+# set this tag to YES in order to let doxygen match functions declarations and 
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. 
+# func(std::string) {}). This also make the inheritance and collaboration 
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC 
+# tag is set to YES, then doxygen will reuse the documentation of the first 
+# member in the group (if any) for the other members of the group. By default 
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of 
+# the same type (for instance a group of public functions) to be put as a 
+# subgroup of that type (e.g. under the Public Functions section). Set it to 
+# NO to prevent subgrouping. Alternatively, this can be done per class using 
+# the \nosubgrouping command.
+
+SUBGROUPING            = YES
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in 
+# documentation are documented, even if no documentation was available. 
+# Private class members and static file members will be hidden unless 
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = YES
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class 
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file 
+# will be included in the documentation.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) 
+# defined locally in source files will be included in the documentation. 
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all 
+# undocumented members of documented classes, files or namespaces. 
+# If set to NO (the default) these members will be included in the 
+# various overviews, but no documentation section is generated. 
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all 
+# undocumented classes that are normally visible in the class hierarchy. 
+# If set to NO (the default) these classes will be included in the various 
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all 
+# friend (class|struct|union) declarations. 
+# If set to NO (the default) these declarations will be included in the 
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any 
+# documentation blocks found inside the body of a function. 
+# If set to NO (the default) these blocks will be appended to the 
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation 
+# that is typed after a \internal command is included. If the tag is set 
+# to NO (the default) then the documentation will be excluded. 
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate 
+# file names in lower-case letters. If set to YES upper-case letters are also 
+# allowed. This is useful if you have classes or files whose names only differ 
+# in case and if your file system supports case sensitive file names. Windows 
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen 
+# will show members with their full class and namespace scopes in the 
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = YES
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen 
+# will put a list of the files that are included by a file in the documentation 
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] 
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen 
+# will sort the (detailed) documentation of file and class members 
+# alphabetically by member name. If set to NO the members will appear in 
+# declaration order.
+
+SORT_MEMBER_DOCS       = NO
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the 
+# brief documentation of file, namespace and class members alphabetically 
+# by member name. If set to NO (the default) the members will appear in 
+# declaration order.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be 
+# sorted by fully-qualified names, including namespaces. If set to 
+# NO (the default), the class list will be sorted only by class name, 
+# not including the namespace part. 
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the 
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or 
+# disable (NO) the todo list. This list is created by putting \todo 
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or 
+# disable (NO) the test list. This list is created by putting \test 
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or 
+# disable (NO) the bug list. This list is created by putting \bug 
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or 
+# disable (NO) the deprecated list. This list is created by putting 
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional 
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       = 
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines 
+# the initial value of a variable or define consists of for it to appear in 
+# the documentation. If the initializer consists of more lines than specified 
+# here it will be hidden. Use a value of 0 to hide initializers completely. 
+# The appearance of the initializer of individual variables and defines in the 
+# documentation can be controlled using \showinitializer or \hideinitializer 
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated 
+# at the bottom of the documentation of classes and structs. If set to YES the 
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = YES
+
+# If the sources in your project are distributed over multiple directories 
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy 
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES       = NO
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that 
+# doxygen should invoke to get the current version for each file (typically from the 
+# version control system). Doxygen will invoke the program by executing (via 
+# popen()) the command <command> <input-file>, where <command> is the value of 
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file 
+# provided by doxygen. Whatever the program writes to standard output 
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER    = 
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated 
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = YES
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are 
+# generated by doxygen. Possible values are YES and NO. If left blank 
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings 
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will 
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for 
+# potential errors in the documentation, such as not documenting some 
+# parameters in a documented function, or documenting parameters that 
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be abled to get warnings for 
+# functions that are documented, but have no documentation for their parameters 
+# or return value. If set to NO (the default) doxygen will only warn about 
+# wrong or incomplete parameter documentation, but not about the absence of 
+# documentation.
+
+WARN_NO_PARAMDOC       = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that 
+# doxygen can produce. The string should contain the $file, $line, and $text 
+# tags, which will be replaced by the file and line number from which the 
+# warning originated and the warning text. Optionally the format may contain 
+# $version, which will be replaced by the version of the file (if it could 
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning 
+# and error messages should be written. If left blank the output is written 
+# to stderr.
+
+WARN_LOGFILE           = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain 
+# documented source files. You may enter file names like "myfile.cpp" or 
+# directories like "/usr/src/myproject". Separate the files or directories 
+# with spaces.
+
+#INPUT                  = net.c signal.c db.h db.c ringbuffer.c ringbuffer.h stat.c afs.c afs.h string.c net.h filter.h filter_chain.c error.h recv.h http_recv.c ortp_recv.c recv_common.c http.h mp3dec.c oggdec.c ortp.h wav.c compress.c daemon.c daemon.h grab_client.c grab_client.h close_on_fork.c close_on_fork.h audiod.c audiod.h time.c mysql.c server.h command.c server.c send.h http_send.c ortp_send.c http.h ortp.h mp3.c ogg.c dopey.c string.h exec.c
+
+INPUT = .
+
+# If the value of the INPUT tag contains directories, you can use the 
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank the following patterns are tested: 
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx 
+# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py
+
+FILE_PATTERNS          = *.c *.h
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories 
+# should be searched for input files as well. Possible values are YES and NO. 
+# If left blank NO is used.
+
+RECURSIVE              = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should 
+# excluded from the INPUT source files. This way you can easily exclude a 
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE                =
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or 
+# directories that are symbolic links (a Unix filesystem feature) are excluded 
+# from the input.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the 
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude 
+# certain files from those directories. Note that the wildcards are matched 
+# against the file with absolute path, so to exclude all test directories 
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       =  list.h *.cmdline.* krell.* gui* SFont* play.c gcc-compat.h rc4.h recv.c para.h fade.c config.h sdl_gui.c filter.c slider.c dbadm.c
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or 
+# directories that contain example code fragments that are included (see 
+# the \include command).
+
+EXAMPLE_PATH           = 
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the 
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank all files are included.
+
+EXAMPLE_PATTERNS       = 
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be 
+# searched for input files to be used with the \include or \dontinclude 
+# commands irrespective of the value of the RECURSIVE tag. 
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or 
+# directories that contain image that are included in the documentation (see 
+# the \image command).
+
+IMAGE_PATH             = 
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should 
+# invoke to filter for each input file. Doxygen will invoke the filter program 
+# by executing (via popen()) the command <filter> <input-file>, where <filter> 
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an 
+# input file. Doxygen will then use the output that the filter program writes 
+# to standard output.  If FILTER_PATTERNS is specified, this tag will be 
+# ignored.
+
+INPUT_FILTER           = 
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern 
+# basis.  Doxygen will compare the file name with each pattern and apply the 
+# filter if there is a match.  The filters are a list of the form: 
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further 
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER 
+# is applied to all files.
+
+FILTER_PATTERNS        = 
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using 
+# INPUT_FILTER) will be used to filter the input files when producing source 
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will 
+# be generated. Documented entities will be cross-referenced with these sources. 
+# Note: To get rid of all source code in the generated output, make sure also 
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER         = YES
+
+# Setting the INLINE_SOURCES tag to YES will include the body 
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct 
+# doxygen to hide any special comment blocks from generated source code 
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES (the default) 
+# then for each documented function all documented 
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES (the default) 
+# then for each documented function all documented entities 
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code 
+# will point to the HTML generated by the htags(1) tool instead of doxygen 
+# built-in source browser. The htags tool is part of GNU's global source 
+# tagging system (see http://www.gnu.org/software/global/global.html). You 
+# will need version 4.8.6 or higher.
+
+USE_HTAGS              = YES
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen 
+# will generate a verbatim copy of the header file for each class for 
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index 
+# of all compounds will be generated. Enable this if the project 
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then 
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns 
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all 
+# classes will be put under the same header in the alphabetical index. 
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that 
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will 
+# generate HTML output.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for 
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank 
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard header.
+
+HTML_HEADER            = web/header2.html
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard footer.
+
+HTML_FOOTER            = web/footer.html
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading 
+# style sheet that is used by each HTML page. It can be used to 
+# fine-tune the look of the HTML output. If the tag is left blank doxygen 
+# will generate a default style sheet. Note that doxygen will try to copy 
+# the style sheet file to the HTML output directory, so don't put your own 
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET        = 
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, 
+# files or namespaces will be aligned in HTML using tables. If set to 
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS     = YES
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files 
+# will be generated that can be used as input for tools like the 
+# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) 
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can 
+# be used to specify the file name of the resulting .chm file. You 
+# can add a path in front of the file if the result should not be 
+# written to the html output directory.
+
+CHM_FILE               = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can 
+# be used to specify the location (absolute path including file name) of 
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run 
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag 
+# controls if a separate .chi index file is generated (YES) or that 
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag 
+# controls whether a binary table of contents is generated (YES) or a 
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members 
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = NO
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at 
+# top of each HTML page. The value NO (the default) enables the index and 
+# the value YES disables it.
+
+DISABLE_INDEX          = NO
+
+# This tag can be used to set the number of enum values (range [1..20]) 
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be
+# generated containing a tree-like index structure (just like the one that 
+# is generated for HTML Help). For this to work a browser that supports 
+# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, 
+# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are 
+# probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW      = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be 
+# used to set the initial width (in pixels) of the frame in which the tree 
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will 
+# generate Latex output.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be 
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to 
+# generate index for LaTeX. If left blank `makeindex' will be used as the 
+# default command name.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact 
+# LaTeX documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used 
+# by the printer. Possible values are: a4, a4wide, letter, legal and 
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE             = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX 
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES         = 
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for 
+# the generated latex document. The header should contain everything until 
+# the first chapter. If it is left blank doxygen will generate a 
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER           = 
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated 
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will 
+# contain links (just like the HTML output) instead of page references 
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS         = NO
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of 
+# plain latex in the generated Makefile. Set this option to YES to get a 
+# higher quality PDF documentation.
+
+USE_PDFLATEX           = NO
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. 
+# command to the generated LaTeX files. This will instruct LaTeX to keep 
+# running if errors occur, instead of asking the user for help. 
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE        = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not 
+# include the index chapters (such as File Index, Compound Index, etc.) 
+# in the output.
+
+LATEX_HIDE_INDICES     = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output 
+# The RTF output is optimized for Word 97 and may not look very pretty with 
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact 
+# RTF documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated 
+# will contain hyperlink fields. The RTF file will 
+# contain links (just like the HTML output) instead of page references. 
+# This makes the output suitable for online browsing using WORD or other 
+# programs which support those fields. 
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's 
+# config file, i.e. a series of assignments. You only have to provide 
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE    = 
+
+# Set optional variables used in the generation of an rtf document. 
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE    = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will 
+# generate man pages
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to 
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output, 
+# then it will generate one additional man file for each entity 
+# documented in the real man page(s). These additional files 
+# only source the real man page, but without them the man command 
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will 
+# generate an XML file that captures the structure of 
+# the code including all documentation.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT             = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_SCHEMA             = 
+
+# The XML_DTD tag can be used to specify an XML DTD, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_DTD                = 
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will 
+# dump the program listings (including syntax highlighting 
+# and cross-referencing information) to the XML output. Note that 
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will 
+# generate an AutoGen Definitions (see autogen.sf.net) file 
+# that captures the structure of the code including all 
+# documentation. Note that this feature is still experimental 
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will 
+# generate a Perl module file that captures the structure of 
+# the code including all documentation. Note that this 
+# feature is still experimental and incomplete at the 
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate 
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able 
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be 
+# nicely formatted so it can be parsed by a human reader.  This is useful 
+# if you want to understand what is going on.  On the other hand, if this 
+# tag is set to NO the size of the Perl module output will be much smaller 
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file 
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. 
+# This is useful so different doxyrules.make files included by the same 
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX = 
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor   
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will 
+# evaluate all C-preprocessor directives found in the sources and include 
+# files.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro 
+# names in the source code. If set to NO (the default) only conditional 
+# compilation will be performed. Macro expansion can be done in a controlled 
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES 
+# then the macro expansion is limited to the macros specified with the 
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files 
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that 
+# contain include files that are not input files but should be processed by 
+# the preprocessor.
+
+INCLUDE_PATH           = 
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard 
+# patterns (like *.h and *.hpp) to filter out the header-files in the 
+# directories. If left blank, the patterns specified with FILE_PATTERNS will 
+# be used.
+
+INCLUDE_FILE_PATTERNS  = 
+
+# The PREDEFINED tag can be used to specify one or more macro names that 
+# are defined before the preprocessor is started (similar to the -D option of 
+# gcc). The argument of the tag is a list of macros of the form: name 
+# or name=definition (no spaces). If the definition and the = are 
+# omitted =1 is assumed. To prevent a macro definition from being 
+# undefined via #undef or recursively expanded use the := operator 
+# instead of the = operator.
+
+PREDEFINED             = HAVE_MAD HAVE_ORTP HAVE_OGGVORBIS HAVE_MYSQL __GNUC__=4 __GNUC_MINOR__=4
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then 
+# this tag can be used to specify a list of macro names that should be expanded. 
+# The macro definition that is found in the sources will be used. 
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED      = 
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then 
+# doxygen's preprocessor will remove all function-like macros that are alone 
+# on a line, have an all uppercase name, and do not end with a semicolon. Such 
+# function macros are typically used for boiler-plate code, and will confuse 
+# the parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references   
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles. 
+# Optionally an initial location of the external documentation 
+# can be added for each tagfile. The format of a tag file without 
+# this location is as follows: 
+#   TAGFILES = file1 file2 ... 
+# Adding location for the tag files is done as follows: 
+#   TAGFILES = file1=loc1 "file2 = loc2" ... 
+# where "loc1" and "loc2" can be relative or absolute paths or 
+# URLs. If a location is present for each tag, the installdox tool 
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen 
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES               = 
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create 
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       = 
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed 
+# in the class index. If set to NO only the inherited external classes 
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed 
+# in the modules index. If set to NO, only the current project's groups will 
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script 
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool   
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will 
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base 
+# or super classes. Setting the tag to NO turns the diagrams off. Note that 
+# this option is superseded by the HAVE_DOT option below. This is only a 
+# fallback. It is recommended to install and use dot, since it yields more 
+# powerful graphs.
+
+CLASS_DIAGRAMS         = YES
+
+# If set to YES, the inheritance and collaboration graphs will hide 
+# inheritance and usage relations if the target is undocumented 
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is 
+# available from the path. This tool is part of Graphviz, a graph visualization 
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section 
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = NO
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect inheritance relations. Setting this tag to YES will force the 
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect implementation dependencies (inheritance, containment, and 
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and 
+# collaboration diagrams in a style similar to the OMG's Unified Modeling 
+# Language.
+
+UML_LOOK               = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the 
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT 
+# tags are set to YES then doxygen will generate a graph for each documented 
+# file showing the direct and indirect include dependencies of the file with 
+# other documented files.
+
+INCLUDE_GRAPH          = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and 
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each 
+# documented header file showing the documented files that directly or 
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will 
+# generate a call dependency graph for every global function or class method. 
+# Note that enabling this option will significantly increase the time of a run. 
+# So in most cases it will be better to enable call graphs for selected 
+# functions only using the \callgraph command.
+
+CALL_GRAPH             = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen 
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES 
+# then doxygen will show the dependencies a directory has on other directories 
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images 
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT       = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be 
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH               = 
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that 
+# contain dot files that are included in the documentation (see the 
+# \dotfile command).
+
+DOTFILE_DIRS           = 
+
+# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width 
+# (in pixels) of the graphs generated by dot. If a graph becomes larger than 
+# this value, doxygen will try to truncate the graph, so that it fits within 
+# the specified constraint. Beware that most browsers cannot cope with very 
+# large images.
+
+MAX_DOT_GRAPH_WIDTH    = 1024
+
+# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height 
+# (in pixels) of the graphs generated by dot. If a graph becomes larger than 
+# this value, doxygen will try to truncate the graph, so that it fits within 
+# the specified constraint. Beware that most browsers cannot cope with very 
+# large images.
+
+MAX_DOT_GRAPH_HEIGHT   = 1024
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the 
+# graphs generated by dot. A depth value of 3 means that only nodes reachable 
+# from the root by following a path via at most 3 edges will be shown. Nodes 
+# that lay further from the root node will be omitted. Note that setting this 
+# option to 1 or 2 may greatly reduce the computation time needed for large 
+# code bases. Also note that a graph may be further truncated if the graph's 
+# image dimensions are not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH 
+# and MAX_DOT_GRAPH_HEIGHT). If 0 is used for the depth value (the default), 
+# the graph is not depth-constrained.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent 
+# background. This is disabled by default, which results in a white background. 
+# Warning: Depending on the platform used, enabling this option may lead to 
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to 
+# read).
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output 
+# files in one run (i.e. multiple -o and -T options on the command line). This 
+# makes dot run faster, but since only newer versions of dot (>1.8.10) 
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will 
+# generate a legend page explaining the meaning of the various boxes and 
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will 
+# remove the intermediate dot files that are used to generate 
+# the various graphs.
+
+DOT_CLEANUP            = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to the search engine   
+#---------------------------------------------------------------------------
+
+# The SEARCHENGINE tag specifies whether or not a search engine should be 
+# used. If set to NO the values of all tags below this one will be ignored.
+
+SEARCHENGINE           = YES
diff --git a/FEATURES b/FEATURES
new file mode 100644 (file)
index 0000000..3dde8e9
--- /dev/null
+++ b/FEATURES
@@ -0,0 +1,71 @@
+Features
+========
+
+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.
+
+       para_audiod runs on the client side and connects to
+       para_server. The audio stream is read back and sent through
+       any of paraslash's filters (mp3 decoder, ogg vorbis decoder,
+       volume normalizer,...) and the resulting stream is written to
+       an external program's standard in, usually an audio player,
+       like para_play that uses alsa. It is possible to grab the
+       stream at any position in the filter chain.
+
+       The receiving/filtering software is also available as
+       standalone command line tool: para_recv grabs the http or ortp
+       stream and writes to stdout; para_filter reads from stdin,
+       converts the stream according to the given --filter command
+       line options and writes the transformed stream to stdout.
+
+mysql-based audio file selector:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+       the (optional) mysql database tool manages some statistics on
+       your audio files. It contacts the mysql server to decide which
+       song to play next by sending a user-defined sql-query. This
+       allows rather sophisticated configurations and is explained
+       in detail in README.mysql.
+
+small memory footprint:
+~~~~~~~~~~~~~~~~~~~~~~~
+       paraslash is lightweight. The stripped binary of para_server
+       with all its features compiled in (mysql/dopey dbtool,
+       mp3/ogg support, http/ortp support) is about 100K on i386
+       under Linux. para_audiod is even smaller.
+
+command line interface, including shell:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+       para_client without arguments starts interactive (shell)
+       mode. Otherwise, command is sent to para_server directly
+       and output is dumped to stdout. This can be used by any
+       scripting language to produce user interfaces with very little
+       programming effort.
+
+authentication/encryption via openssl:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+       All connections between para_server and para_client are
+       encrypted by default.  For each user of paraslash you must
+       create a public/secret key pair for authentication/encryption.
+
+various user interfaces and utilities:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+       o para_gui. Curses based interface, displays information in a
+        curses window and can be used to easily control para_server
+       and para_audiod.
+
+       o para_sdl_gui. Shows pictures (on a per song basis) and
+       other information about the current audio file. Can be used
+       as a screen saver.
+
+       o para_krell. A gkrellm2 plugin that shows small pics and is
+       able to launch 27 different commands by clicking on the image.
+
+       o para_slider. User-friendly stream creator for people who
+       don't like their keyboard.
+
+       o para_dbadm. Simple curses interface for changing attributes.
+
+       o para_fade. Simple volume fader and alarm clock.
diff --git a/GPL b/GPL
new file mode 100644 (file)
index 0000000..60549be
--- /dev/null
+++ b/GPL
@@ -0,0 +1,340 @@
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                       59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+\f
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+\f
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+\f
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                           NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+\f
+           How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) 19yy  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) 19yy name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/INSTALL b/INSTALL
new file mode 100644 (file)
index 0000000..3b37318
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,212 @@
+Paraslash install notes
+=======================
+
+Any knowledge of how to work with mouse and icons is not required.
+
+Install all needed packages
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+See README for a list of required software. Don't be afraid of the long
+list of unusal libraries: Most of them are only needed for optional
+programs. Autoconf will detect what is installed on your system and
+will only build those executables that can be built with your setup.
+
+
+Install server and client
+~~~~~~~~~~~~~~~~~~~~~~~~~
+Install the package on all machines, you'd like this software to run on:
+
+       (./configure && make) > /dev/null
+
+There should be no errors (but probably many warnings about missing
+software). Then, as root,
+
+       make install
+
+
+Setup user list and create rsa keys
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+If you already have your rsa keys, skip this step. If you are new
+to paraslash, you have to generate an rsa key pair for each user you
+want to allow to connect. You need at least one user.
+
+Let's assume that you'd like to run the server on host server_host
+as user foo, and that you want to connect from client_host as user bar.
+
+As foo@server_host, create ~/.paraslash/server.users:
+
+       target=~/.paraslash/server.users
+       key=~/.paraslash/key.pub.bar
+       perms=DB_READ,DB_WRITE,AFS_READ,AFS_WRITE
+       mkdir -p ~/.paraslash
+       echo "user bar $key $perms" >> $target
+
+This gives bar full privileges.
+
+Change to the bar account on client_host and generate the key-pair
+with the commands
+
+       key=~/.paraslash/key.bar
+       mkdir -p ~/.paraslash
+       (umask 077 && openssl genrsa -out $key)
+
+Next, extract its public part:
+
+       pubkey=~/.paraslash/key.pub.bar
+       openssl rsa -in $key -pubout -out $pubkey
+
+and copy the public key just created to server_host:
+
+       scp $pubkey foo@server_host:.paraslash/
+
+Finally, tell para_client to connect to server_host:
+
+       echo 'hostname server_host' > ~/.paraslash/client.conf
+
+Start para_server
+~~~~~~~~~~~~~~~~~
+       para_server
+
+Now you can use para_client to connect to the server and issue
+commands. Open a new shell (as "bar" on "client_host" in the above
+example) and try
+
+       para_client help
+       para_client si
+
+to retrieve the list of available commands and some server info.
+
+
+Choose your database tool (dbtool)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+You have three options:
+
+       1. Use the mysql dbtool which comes with paraslash and requires
+       mysql. This is recommended.
+
+       2. Use your own database tool. If you have that already,
+       skip this step.
+
+       3. If you can not use the mysql dbtool and you just want
+       to quickly make paraslash working, use the dopey dbtool.
+       The directory which is searched for audio files can be given
+       via the server option --dopey_dir.
+
+       Note, however, that dopey is _really_ dopey. It scans
+       $dopey_dir on every audio file change and chooses one
+       randomly. You get the idea. Have a look at its source code
+       and feel free to modify.
+
+
+The current database tool can be changed at runtime via
+
+       para_client cdt new_dbtool
+
+If you have choosen 1. above, read README.mysql and follow the
+instructions given there.  Return to this document when ready.
+
+
+Start streaming manually
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+       para_client play
+       para_client stat 2
+
+This starts streaming and dumps some information on the current song
+to stdout.
+
+You should now be able to listen to the stream with any player
+capable of reading from stdin. To check this, try the following
+on client_host:
+
+       mp3:
+
+               para_recv -r http:-i:server_host | para_filter -f mp3 -f wav | para_play
+               or
+               mpg123 http://server_host:8000/
+               or
+               xmms http://server_host:8000/
+
+       ogg:
+
+               para_recv -r http:-i:server_host | para_filter -f ogg -f wav | para_play
+
+If this works, proceede. Otherwise doublecheck what is logged by
+para_server and use the --loglevel option of para_recv to increase
+verbosity.
+
+Configure para_audiod
+~~~~~~~~~~~~~~~~~~~~~
+In order to automatically start the right decoder at the right time
+and to offer to the clients some information on the current audio
+stream and on paraslash's internal state, you should run the local
+audio daemon, para_audiod, on every machine that is supposed to play
+the audio stream. Try
+
+       para_audiod -h
+
+for help. Usually you have to specify at least server_host as the
+receiver specifier, like this:
+
+       -r http:-i:server_host
+
+The prefered way to use para_audiod is to run it once at system start
+as an unprivileged user. para_audiod needs to create a "well-known"
+socket for the clients to connect to. If you want to change the
+default socket (e.g. because you do not have write access for the
+directory where the socket resides), use the -s option or the config
+file to change the default. Note that in this case you'll also have
+to specify the same value for para_audioc's -s option.
+
+If para_server is playing, you should be able to listen to the audio
+stream as soon as para_audiod is started.  Once it is running, try
+
+       para_audioc stat
+
+That should dump some information to stdout. Other commands include
+
+       para_audioc off
+       para_audioc on
+       para_audioc sb
+       para_audioc term
+       para_audioc cycle
+
+
+Start para_gui
+~~~~~~~~~~~~~~
+para_gui reads the output of "para_audioc stat" and displays that
+information in a curses window. It also allows you to bind keys to
+arbitrary commands. There are several flavours of key-bindings:
+
+       o internal: These are the built-in commands that can not be
+         changed (help, quit, loglevel, version...).
+
+        o external: Shutdown curses before launching the given command.
+          Useful for starting other ncurses programs from within
+          para_gui, e.g. aumix or para_dbadm. Or, use
+
+               para_client mbox
+
+         to write a mailbox containing one mail for each file
+          in the mysql database and start mutt from within para_gui
+          to browse your collection!
+
+       o display: Launch the command and display its stdout in
+         para_gui's bottom window.
+
+       o para: Like display, but start "para_client <specified
+         command>" instead of "<specified command>".
+
+
+That's all, congratulations. Check out all the other optional gimmics!
+
+Troubles?
+~~~~~~~~~
+If something went wrong, look at the output. If that does not give
+you a clue, use loglevel one (option -l 1 for most commands) to show
+debugging info. Almost all paraslash executables have a brief online
+help which is displayed by using the -h switch.
+
+Still not working? Mail the author Andre Noll <maan@systemlinux.org>
+(english, german, or spanish language). Please provide enough info
+such as the version of paraslash you are using and relevant parts of
+the logs.
diff --git a/Makefile.in b/Makefile.in
new file mode 100644 (file)
index 0000000..25cec46
--- /dev/null
@@ -0,0 +1,277 @@
+COPYRIGHT = Copyright (c) 1997-2006 by Andre Noll
+DISCLAIMER = This is free software with ABSOLUTELY NO WARRANTY. See COPYING for details.
+
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+
+BINDIR = @bindir@
+VARDIR = /var/paraslash
+PKGDATADIR = @datadir@/@PACKAGE_NAME@
+CONFDIR = $(PKGDATADIR)/samples
+FONTDIR = $(PKGDATADIR)/fonts
+PICDIR = $(PKGDATADIR)/pics
+MANDIR = @prefix@/share/man/man1
+
+install_sh = @install_sh@
+SSL_LIBS = @SSL_LIBS@
+SSL_LDFLAGS = @SSL_LDFLAGS@
+
+extra_binaries = @extra_binaries@
+build_date = $(shell date)
+system = $(shell uname -rs)
+cc_version = $(shell $(CC) --version | head -n 1)
+version = @PACKAGE_VERSION@
+codename = atomic duality
+
+DEBUG_CPPFLAGS += -Wno-sign-compare -g -Wunused -Wundef -W
+
+# produces lots of warnings on older debian systems with gcc-2.95.4
+# DEBUG_CPPFLAGS += -Wredundant-decls
+
+# produces false positives
+# DEBUG_CPPFLAGS += -Wunreachable-code
+
+# invalid options for gcc-2.95.4
+# CPPFLAGS += -Wfloat-equal
+# CPPFLAGS += -Wmissing-format-attribute
+# CPPFLAGS += -Wunused-macros
+
+# invalid option for gcc-3.3.5
+# CPPFLAGS += -Wextra
+
+CPPFLAGS += -Os
+CPPFLAGS += -Wall
+CPPFLAGS += -Wuninitialized
+CPPFLAGS += -Wstrict-prototypes
+CPPFLAGS += -Wchar-subscripts
+CPPFLAGS += -Wformat-security
+CPPFLAGS += -DBINDIR='"$(BINDIR)"'
+CPPFLAGS += -DFONTDIR='"$(PKGDATADIR)/fonts"'
+CPPFLAGS += -DPICDIR='"$(PKGDATADIR)/pics"'
+CPPFLAGS += -DBUILD_DATE='"$(build_date)"'
+CPPFLAGS += -DSYSTEM='"$(system)"'
+CPPFLAGS += -DVERSION='"$(version)"'
+CPPFLAGS += -DCODENAME='"$(codename)"'
+CPPFLAGS += -DCC_VERSION='"$(cc_version)"'
+CPPFLAGS += -Werror-implicit-function-declaration
+
+BINARIES = para_server para_client para_gui para_audiod para_audioc para_recv para_filter $(extra_binaries)
+
+FONTS := $(wildcard fonts/*.png)
+PICS := $(wildcard pics/paraslash/*.jpg)
+MANS := $(wildcard doc/man/man1/*.1)
+sample_conf := $(wildcard *.conf.sample)
+gengetopts := $(wildcard *.ggo)
+gengetopts_c := $(gengetopts:.ggo=.cmdline.c)
+gengetopts_h := $(gengetopts:.ggo=.cmdline.h)
+all_c_files := $(wildcard *.c)
+c_sources := $(filter-out $(gengetopts_c), $(all_c_files))
+grutatxt := COPYING ChangeLog NEWS README.mysql CREDITS INSTALL README \
+       FEATURES GPL
+grutatxt_html := $(grutatxt:=.html)
+html_in := $(wildcard web/*.in.html)
+gen_html := $(subst web/,web/sync/,$(html_in))
+gen_html := $(gen_html:.in.html=.html)
+gruta_in := $(grutatxt:=.in.html)
+gruta_in := $(patsubst %,web/%,$(gruta_in))
+gruta_html := $(grutatxt:=.html)
+gruta_html := $(patsubst %,web/sync/%,$(gruta_html))
+shots := gui-2005-11-12.png para_audiod-startup.txt
+shots += para_krell-2005-02.png para_server-startup.txt
+shots += para_slider-2004-12.png sdl_gui.jpg para_krell-2005-02.png
+shots := $(patsubst %,web/sync/%,$(shots))
+web_pics := web/sync/paraslash.png web/sync/paraslash.ico
+web_misc := demo-script overview.pdf versions/paraslash-cvs.tar.bz2 PUBLIC_KEY key.anonymous para.css doc
+web_misc := $(patsubst %,web/sync/%,$(web_misc))
+
+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
+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) \
+       $(misc) $(grutatxt) $(gengetopts) $(autocrap) $(gengetopts_c) $(gengetopts_h) \
+       $(scripts)
+
+.PHONY: clean distclean maintainer-clean install html www tags ChangeLog doxygen
+all: $(BINARIES)
+www: $(gen_html) $(gruta_html) $(web_pics) $(web_misc) $(shots) tags doxygen
+
+client_objs = client.cmdline.o client.o net.o string.o crypt.o
+gui_objs = gui.cmdline.o gui.o gui_common.o exec.o close_on_fork.o signal.o string.o gui_theme.o stat.o ringbuffer.o
+sdl_gui_objs = sdl_gui.cmdline.o SFont.o sdl_gui.o gui_common.o exec.o close_on_fork.o string.o stat.o
+dbadm_objs = dbadm.o exec.o close_on_fork.o string.o
+fade_objs = fade.cmdline.o fade.o exec.o close_on_fork.o string.o
+krell_objs = krell.o string.o
+slider_objs = slider.o string.o
+audioc_objs = audioc.cmdline.o audioc.o string.o net.o
+play_objs = play.cmdline.o play.o time.o
+
+*.o: para.h config.h gcc-compat.h
+
+include Makefile.deps
+
+V := ($(TGZ_PREFIX)@PACKAGE_STRING@, $(codename))\n$(COPYRIGHT)\n$(DISCLAIMER)
+module_ggo_opts := --set-version="" --set-package=""
+
+grab_client.cmdline.h grab_client.cmdline.c: grab_client.ggo
+       gengetopt $(module_ggo_opts) \
+               --no-handle-error \
+               --no-handle-help \
+               --arg-struct-name=grab_client_args_info \
+               --file-name=$(subst .ggo,,$<).cmdline \
+               --func-name $(subst .ggo,,$<)_cmdline_parser < $<
+       grep -v 'fprintf\ *(stderr' $(subst .ggo,,$<).cmdline.c > $(subst .ggo,,$<).tmp
+       mv $(subst .ggo,,$<).tmp $(subst .ggo,,$<).cmdline.c
+       ggo_help GRAB_HELP_TXT < grab_client.ggo >> $(subst .ggo,,$<).cmdline.h
+
+%_recv.cmdline.h %_recv.cmdline.c: %_recv.ggo
+       gengetopt $(module_ggo_opts) \
+               --file-name=$(subst .ggo,,$<).cmdline \
+               --func-name $(subst .ggo,,$<)_cmdline_parser < $<
+
+%_filter.cmdline.h %_filter.cmdline.c: %_filter.ggo
+       gengetopt $(module_ggo_opts) \
+               --file-name=$(subst .ggo,,$<).cmdline \
+               --func-name $(subst _filter.ggo,,$<)_cmdline_parser < $<
+
+%.cmdline.h %.cmdline.c: %.ggo
+       case $< in client.ggo) O="--unamed-opts=command";; \
+               audioc.ggo) O="--unamed-opts=command";; \
+       esac; \
+       gengetopt $$O --conf-parser --file-name=$(*F).cmdline --set-package="para_$(subst .cmdline,,$(*F))" --set-version="$V"  < $<
+
+ortp_send.o: ortp_send.c
+       $(CC) -c -Wall -o $@ -g @GLIB_CFLAGS@ $<
+ortp_recv.o: ortp_recv.c
+       $(CC) -c -Wall -o $@ -g @GLIB_CFLAGS@ $<
+
+para_recv: @recv_objs@
+       $(CC) @recv_objs@ -o $@ @recv_ldflags@
+
+slider.o: slider.c
+       $(CC) -c -Wall -o $@ -g @GLIB_CFLAGS@  @GTK_CFLAGS@ $<
+
+krell.o: krell.c
+       $(CC) -Wall -O -g -fPIC @GTK_CFLAGS@ -c -o $@ krell.c
+
+%.cmdline.o: %.cmdline.c
+       $(CC) -c $(CPPFLAGS) $<
+%.o: %.c
+       $(CC) -c $(CPPFLAGS) $(DEBUG_CPPFLAGS) $<
+
+para_filter: @filter_objs@
+       $(CC) @filter_objs@ -o $@ @filter_ldflags@
+
+para_slider: $(slider_objs)
+       $(CC) $(slider_objs) -o $@ @GTK_LIBS@ @GLIB_LIBS@ -lzmw
+
+para_client: $(client_objs)
+       $(CC) -o $@ $(client_objs) $(SSL_LDFLAGS) -lreadline -lncurses $(SSL_LIBS)
+
+para_gui: $(gui_objs)
+       $(CC) -o $@ $(gui_objs) -lncurses
+
+para_audiod: @audiod_objs@
+       $(CC) -o $@ @audiod_objs@ @audiod_ldflags@
+
+para_audioc: $(audioc_objs)
+       $(CC) -o $@ $(audioc_objs)
+
+para_dbadm: $(dbadm_objs)
+       $(CC) -o $@ $(dbadm_objs) -lncurses -lmenu
+
+para_fade: $(fade_objs)
+       $(CC) -o $@ $(fade_objs)
+
+para_server: @server_objs@
+       $(CC) -o $@ @server_objs@  @server_ldflags@
+
+para_sdl_gui: $(sdl_gui_objs)
+       $(CC) -o $@ $(sdl_gui_objs) -lSDL_image
+
+para_play: $(play_objs)
+       $(CC) -o $@ $(play_objs) -lasound
+
+para_compress: $(compress_objs)
+       $(CC) -o $@ $(compress_objs)
+
+para_krell.so: $(krell_objs)
+       $(CC) -Wall -fPIC @GTK_CFLAGS@ krell.o -o $@ @GTK_LIBS@ -shared
+
+clean:
+       rm -f *.o $(BINARIES)
+
+distclean: clean
+       rm -f Makefile autoscan.log config.status config.log && \
+       rm -rf web/sync/* autom4te.cache aclocal.m4
+       rm -f GPATH GRTAGS GSYMS GTAGS
+
+maintainer-clean: distclean
+       rm -f $(gengetopts_c) $(gengetopts_h) *.tar.bz2 \
+               $(grutatxt_html) ChangeLog* config.h configure \
+               config.h.in
+
+install: all
+       umask 022 && \
+       mkdir -p $(BINDIR) $(VARDIR) $(VARDIR)/fifo && \
+       $(install_sh) -s -m 755 $(BINARIES) $(BINDIR) && \
+       mkdir -p $(CONFDIR) && \
+       $(install_sh) -m 644 bash_completion $(sample_conf) $(CONFDIR)
+       mkdir -p $(FONTDIR) && \
+       $(install_sh) -m 644 $(FONTS) $(FONTDIR) && \
+       mkdir -p $(PICDIR) && \
+       $(install_sh) -m 644 $(PICS) $(PICDIR) && \
+       mkdir -p $(MANDIR) && \
+       $(install_sh) -m 644 $(MANS) $(MANDIR)
+
+
+@PACKAGE_TARNAME@-@PACKAGE_VERSION@.tar.bz2: all $(gengetopts_c) $(tarball)
+       dir=@PACKAGE_TARNAME@-@PACKAGE_VERSION@ &&\
+       mkdir -p $${dir} && \
+       cp -a $(tarball) $${dir} && \
+       find $${dir} -name "CVS" | xargs rm -rf && \
+       tar cpjf $@ $${dir} && \
+       rm -rf $${dir} && \
+       ls -l $@
+
+ChangeLog:
+       para_util changelog > $@
+web/%.in.html: %
+       grutatxt -nb < $< > $@
+tags:
+       rm -rf web/sync/HTML && gtags && htags -nF && mv HTML web/sync
+web/sync/doc:
+       para_util doc
+web/header2.html: web/header.html
+       sed -e 's|href="|href="\.\.\/\.\./|g' \
+               -e 's|SRC="|SRC="\.\.\/\.\./|g' $< > $@
+doxygen: web/header2.html
+       mkdir -p web/sync/doxygen
+       doxygen
+web/sync/doxygen:
+       mkdir -p $@
+web/sync/doxygen/index.html:
+web/sync/%.html: web/%.in.html web/header.html web/footer.html web/sync
+       cat web/header.html $< web/footer.html > $@
+web/sync/%.png: pics/web/%.png web/sync
+       cp $< $@
+web/sync/%.ico: pics/web/%.ico web/sync
+       cp $< $@
+web/sync/demo-script: scripts/demo-script web/sync
+       cp $< $@
+web/sync/para.css: web/para.css web/sync
+       cp $< $@
+web/sync/versions/paraslash-cvs.tar.bz2: paraslash-cvs.tar.bz2 web/sync
+       cp -a versions web/sync && cp $< $@
+web/sync/overview.pdf: skencil/overview.pdf web/sync
+       cp $< $@
+web/sync/%: %
+       cp $< $@
+web/sync/%: pics/screenshots/%
+       cp $< $@
+skencil/%.ps: skencil/%.sk
+       sk2ps $< > $@
+%.pdf: %.ps
+       ps2pdf - - < $< > $@
diff --git a/NEWS b/NEWS
new file mode 100644 (file)
index 0000000..81f317d
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,954 @@
+NEWS
+====
+
+0.?.? (to be announced) "atomic duality"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+       o update to gengetopt-2.16
+
+0.2.10 (2006-02-17) "cyclic attractor"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Huge documentation update, a scrollable window for para_gui, ortp
+improvements, and of course many small fixes not mentioned here.
+The diffstat below is rather misleading as most insertions are due
+to the new source documentation.
+
+       o autoconf cleanup
+
+       o para_server also uses the new error subsystem
+
+       o lots of new documentation (UTSL)
+
+       o gui improvements:
+               - keysyms for cursor keys and for next/previous page keys
+               - scrollable output window
+               - new internal commands: scroll up/down, page up/down
+               - fix color of command output.
+
+       o ortp: the --chunk_time and --header flags are no longer needed
+       for para_recv/para_audiod as this information is now encoded in
+       each rtp packet sent by para_server.
+
+New files: daemon.h net.h ringbuffer.c ringbuffer.h string.h web/para.css
+Doxyfile
+
+ Makefile.in               |   99 ++-----
+ NEWS                      |   19 +
+ README                    |    6
+ README.mysql              |   21 -
+ afs.c                     |  223 +++++++++------
+ afs.h                     |  140 ++++++++--
+ audioc.c                  |    5
+ audiod.c                  |  503 +++++++++++++++++-------------------
+ audiod.h                  |   11
+ client.c                  |   12
+ close_on_fork.c           |   43 +++
+ close_on_fork.h           |    1
+ command.c                 |  283 ++++++++------------
+ compress.c                |   34 +-
+ configure.ac              |  402 +++++++++++++---------------
+ crypt.c                   |   63 ++--
+ crypt.h                   |   12
+ daemon.c                  |   76 ++++-
+ db.c                      |   90 ++++--
+ db.h                      |   97 ++++++
+ dbadm.c                   |   23 +
+ dopey.c                   |   59 ++--
+ error.h                   |  315 +++++++++++++++++-----
+ exec.c                    |   55 +++
+ fade.c                    |    4
+ filter.c                  |   23 -
+ filter.ggo                |    2
+ filter.h                  |  307 +++++++++++++++++++--
+ filter_chain.c            |  147 +++++++---
+ grab_client.c             |   87 ++++--
+ grab_client.h             |   41 +-
+ gui.c                     |  422 ++++++++++++++++++++++--------
+ gui_common.c              |    2
+ http.h                    |    1
+ http_recv.c               |   99 ++++---
+ http_send.c               |  164 ++++++++---
+ list.h                    |    5
+ mp3.c                     |  246 +++++++++--------
+ mp3dec.c                  |   88 +++---
+ mysql.c                   |  339 ++++++++++--------------
+ net.c                     |  271 ++++++++++++++-----
+ ogg.c                     |   86 +++---
+ oggdec.c                  |   86 +++---
+ ortp.h                    |   31 ++
+ ortp_recv.c               |  162 ++++++-----
+ ortp_recv.ggo             |    2
+ ortp_send.c               |  138 +++++----
+ para.h                    |   93 ------
+ play.c                    |   20 -
+ recv.c                    |   11
+ recv.h                    |  147 +++++++++-
+ recv_common.c             |    9
+ sdl_gui.c                 |   21 -
+ send.h                    |   82 +++++
+ server.c                  |  119 +++++---
+ server.h                  |  117 ++++++--
+ signal.c                  |   70 ++++-
+ slider.c                  |    5
+ stat.c                    |   89 +++++-
+ string.c                  |  243 ++++++++++-------
+ time.c                    |   78 ++++-
+ wav.c                     |   26 -
+ web/documentation.in.html |   14 -
+ web/header.html           |   13
+ web/index.in.html         |    1
+ 65 files changed, 4146 insertions(+), 2357 deletions(-)
+
+0.2.9 (2006-01-24) "progressive turbulence"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Internal audiod receivers/filters, the new error subsystem and
+a lot of small improvements.
+
+       o para_recv and para_filter are integrated into the para_audiod
+         binary, i.e. audiod no longer spawns a new process for
+         each receiver/filter. As para_recv and para_filter might be
+         useful as standalone programs, they still get built (linked
+         against the same object files that are also used for audiod).
+
+       o further ortp timing improvements should reduce the CPU usage
+         of the ortp receiver.
+
+       o improved audio grabbing. The 'grab' command of para_audiod
+         has its own set of command line options. Read the output of
+         "para_audioc help grab" for more info.
+
+       o oggdec: configurable input prebuffer size.
+
+       o the new error subsystem gives better error diagnostics
+         and reduces code size.
+
+New files: audiod.h error.h grab_client.c grab_client.ggo grab_client.h
+http_recv.ggo ortp_recv.ggo recv_common.c
+
+ FEATURES                |   49 -
+ INSTALL                 |   22
+ Makefile.in             |   87 +-
+ NEWS                    |   73 ++
+ README                  |   89 +-
+ README.mysql            |   18
+ audioc.c                |    5
+ audiod.c                | 1243 +++++++++++++++-----------------------
+ audiod.ggo              |   97 ++
+ command.c               |    5
+ compress.c              |   44 -
+ compress_filter.ggo     |    4
+ configure.ac            |  127 +++
+ daemon.c                |   12
+ exec.c                  |   34 -
+ filter.c                |   66 +-
+ filter.h                |   24
+ filter_chain.c          |  126 ++-
+ gcc-compat.h            |    8
+ http_recv.c             |   56 +
+ http_send.c             |   35 -
+ mp3.c                   |   12
+ mp3dec.c                |   27
+ net.c                   |  133 ++--
+ oggdec.c                |  108 +--
+ oggdec_filter.ggo       |    3
+ ortp.h                  |    2
+ ortp_recv.c             |  290 +++++---
+ ortp_send.c             |  187 ++---
+ para.h                  |   17
+ play.c                  |   17
+ recv.c                  |  101 +--
+ recv.ggo                |   15
+ recv.h                  |   51 +
+ scripts/demo-script     |   12
+ server.c                |   15
+ signal.c                |   11
+ skencil/overview.sk     |  300 ++++-----
+ stat.c                  |   11
+ string.c                |  170 ++++-
+ wav.c                   |   12
+ web/demo.in.html        |   72 --
+ web/download.in.html    |    3
+ web/index.in.html       |    1
+ web/license.in.html     |    5
+ web/screenshots.in.html |    4
+ 46 files changed, 2042 insertions(+), 1761 deletions(-)
+
+0.2.8 (2006-01-02) "dynamic accumulation"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The new modular filter design and the para_play-hangs bugfix.
+
+       o new executable: para_filter. It combines para_mp3dec,
+         para_oggdec and para_compress. It also adds a further filter
+         type, wav, that just inserts a wave header at the desired point
+         of the filter chain. All 'piping' is done in-memory (i.e. no
+         read/write operations are used).
+
+       o para_play: fix a stupid bug that caused it to hang under
+         certain circumstances.
+
+New files: compress_filter.ggo filter.c filter.ggo file filter.h
+filter_chain.c oggdec_filter.ggo wav.c
+
+ INSTALL           |    4
+ Makefile.in       |   36 +++++---
+ NEWS              |   15 +++
+ README            |   25 ++----
+ command.c         |    8 +
+ compress.c        |  149 ++++++++++++++++++++----------------
+ configure.ac      |   37 +++++---
+ http_recv.c       |  121 ++++++++++++++++++-----------
+ mp3dec.c          |  216 +++++++++++++++++++++-------------------------------
+ oggdec.c          |  223 ++++++++++++++++++++++++++++++++++++------------------
+ ortp_recv.c       |  167 ++++++++++++++++++++--------------------
+ ortp_send.c       |    2
+ play.c            |    2
+ recv.c            |   44 +++++++---
+ recv.ggo          |   12 --
+ recv.h            |   37 +++++++-
+ server.ggo        |    2
+ web/index.in.html |    1
+ 18 files changed, 631 insertions(+), 470 deletions(-)
+
+0.2.7 (2006-12-27) "transparent invariance"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Not many user-visible changes but a fair amount of internal improvements.
+
+
+       o The http sender buffers data if it can not be sent
+         out immediately (because the socket is not writable). This
+         should prevent para_server from shutting down the connection
+         too early on a loaded network.
+
+       o para_play also prebuffers data if it is told to start at a
+         future time by the --start_time option.
+
+       o The return of para_recv: It combines para_ortp_recv and
+         para_http_recv. Use the --receiver option to switch between
+         the two. para_recv builds without libortp, but contains
+         only the http receiver in this case.
+
+       o update to ortp 0.8.1. As this ortp release contains incompatible
+         changes, para_recv-0.2.7 won't link against older ortp libs.
+
+       o improved ortp timings.
+
+       o use of gcc-extensions that #define away for non-gcc and
+         gcc < 3.0.
+
+New files: gcc-compat.h
+
+ CREDITS      |   14 +-
+ FEATURES     |   10 -
+ INSTALL      |   36 +++--
+ Makefile.in  |  171 +++++++++++++++++----------
+ NEWS         |   30 ++++
+ README       |  128 ++++++++------------
+ README.mysql |    4
+ afs.c        |  113 +++++++++++++-----
+ afs.h        |    2
+ audioc.c     |   21 +++
+ audiod.c     |  226 +++++++++++++++++++++---------------
+ audiod.ggo   |    4
+ client.c     |    9 -
+ command.c    |   71 +++++++----
+ compress.c   |   15 +-
+ compress.ggo |    4
+ configure.ac |   30 ++--
+ crypt.c      |    2
+ daemon.c     |    6
+ db.c         |    4
+ dbadm.c      |   46 ++-----
+ dopey.c      |   16 +-
+ fade.c       |    3
+ gui.c        |   77 ++++++------
+ http_recv.c  |  143 +++++++++++++----------
+ http_send.c  |  217 ++++++++++++++++++++++-------------
+ index.html   |  154 +++++++++++++------------
+ list.h       |  361 -----------------------------------------------------------
+ mp3.c        |   17 +-
+ mp3dec.c     |    5
+ mysql.c      |   57 ++++++---
+ net.c        |   12 +
+ ogg.c        |   26 ++--
+ oggdec.c     |   34 +++--
+ ortp.h       |    2
+ ortp_recv.c  |  263 +++++++++++++++++++++---------------------
+ ortp_send.c  |   96 ++++++++++-----
+ para.h       |   51 +++-----
+ play.c       |  173 +++++++++++++++++++---------
+ play.ggo     |    2
+ sdl_gui.c    |   27 +++-
+ send.h       |    2
+ server.c     |  100 ++++++++--------
+ stat.c       |   68 ++++++++---
+ string.c     |   41 +++---
+ 45 files changed, 1500 insertions(+), 1393 deletions(-)
+
+0.2.6 (2005-10-29) "recursive compensation"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Transparent session encryption (uses openssl's Alleged RC4 cipher),
+the internal find command and several other improvements and cleanups.
+
+       o Encrypt the session if encryption is requested by the client
+         (default for para_client 0.2.6). This is backwards
+         compatible, so older clients can still connect to para_server
+         0.2.6. Use the new client option --plain to request an
+         uncrypted session (off by default, must be set to on in
+         order to connect to para_server 0.2.x with 0 <= x <= 5).
+
+       o para_server uses an internal function to locate audio files
+         rather than calling find(1). The server option
+         --mysql_audio_file_dir replaces --mysql_find_cmd.
+
+       o documentation update
+
+       o man pages
+
+       o header file cleanup
+
+       o para_client code cleanup
+
+       o para_gui: faster display of output of display commands
+
+New files: afs.h close_on_fork.c close_on_fork.h db.c db.h rc4.h
+
+ 1.0          |    3
+ INSTALL      |   51 +-
+ Makefile.in  |   53 +--
+ NEWS         |   28 +
+ README       |   27 -
+ README.mysql |   45 +-
+ SFont.c      |   20 -
+ afs.c        |   51 --
+ audioc.c     |    6
+ audiod.c     |  303 +++++++++--------
+ client.c     |  174 +++-------
+ client.ggo   |    1
+ command.c    |  608 ++++++++++++++++++++---------------
+ compress.c   |    3
+ compress.ggo |    4
+ configure.ac |    2
+ crypt.c      |   50 +-
+ crypt.h      |    2
+ dopey.c      |  136 +++++--
+ exec.c       |   39 --
+ fade.c       |    4
+ gui.c        |   74 ++--
+ http_recv.c  |    4
+ http_send.c  |   51 +-
+ index.html   |   12
+ krell.c      |    2
+ mp3.c        |    5
+ mysql.c      | 1008 ++++++++++++++++++++++++++++++++---------------------------
+ net.c        |  122 ++-----
+ ogg.c        |    5
+ ortp_send.c  |   37 +-
+ para.h       |   39 +-
+ send.h       |    2
+ server.c     |   49 +-
+ server.ggo   |    2
+ server.h     |   68 ---
+ stat.c       |  132 ++++---
+ string.c     |   33 -
+ 38 files changed, 1738 insertions(+), 1517 deletions(-)
+
+0.2.5 (2005-10-13) "aggressive_resolution"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+This release adds internal senders, i.e. no more external programs are
+spawned for sending out the audio data. There are two different senders
+available: The http sender and the ortp sender (former para_send which
+is no longer needed).
+
+The new sender code has a plugin-like design so it can be easily
+extended should there be be any future need for supporting another
+network streaming protocol. All senders are completely independent of
+each other. In particular, the http and the ortp sender can operate
+in parallel.
+
+       o new server command: sender to control senders at runtime.
+         Read the output of "para_server -h" and "para_client help
+         sender" for more information.
+
+       o para_recv renamed to para_ortp_recv
+
+       o new executable: para_http_recv, a simple command line
+         http receiver.
+
+       o major afs/mp3/ogg code simplifications due to internal
+         senders.
+
+       o ogg timing improvements
+
+       o fix several minor memory leaks (found by valgrind)
+
+       o empty stream definitions work again
+
+       o com_ne(): ignore errors on remove
+
+       o audiod: fix segfault on server restart
+
+New files: http.h http_recv.c http_recv.ggo http_send.c ortp.h ortp_recv.c
+       ortp_recv.ggo ortp_send.c
+
+ FEATURES            |   27 +++-
+ INSTALL             |   19 +--
+ Makefile.in         |   54 ++++-----
+ NEWS                |   36 ++++++
+ README              |   45 ++++---
+ afs.c               |  311 +++++++++++++++-------------------------------------
+ audioc.c            |   10 +
+ audiod.c            |   82 +++++++------
+ audiod.ggo          |    2
+ command.c           |  184 +++++++++++++++++++++++++++---
+ configure.ac        |   36 ++++--
+ daemon.c            |   10 +
+ exec.c              |   33 -----
+ gui.c               |    6 -
+ index.html          |    6 -
+ mp3.c               |  144 ++++--------------------
+ mysql.c             |   52 ++++----
+ net.c               |   58 ++++++++-
+ ogg.c               |  289 ++++++++++++++----------------------------------
+ oggdec.c            |   19 ++-
+ para.h              |   23 ++-
+ server.c            |  128 ++++++++++++++-------
+ server.ggo          |   17 ++
+ server.h            |   40 ++++--
+ skencil/overview.sk |   86 +++-----------
+ string.c            |   51 +++++---
+ 26 files changed, 870 insertions(+), 898 deletions(-)
+
+
+0.2.4 (2005-09-21) "toxic anticipation"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Several small improvements, fixes and the new grab command.
+
+       o audiod:
+               - new command: "grab" to grab the output of the stream reader
+                 or any filters. Read the output of "para_audioc help grab"
+                 for more information.
+               - fix memory leak
+               - code cleanup
+
+       o audioc: new command line option: --bufsize to specify a
+         buffer size different from the default size 8192.
+
+       o improved error diagnostics for para_play.
+
+       o new configure option: --enable-ssldir so search for openssl in
+         non-standard places
+
+       o sdl_gui: Make it look nice again for 1024x768
+
+       o server: report total size of memory allocated with sbrk by malloc,
+         new command line option: --announce_time
+
+New files: list.h
+
+ Makefile.in  |   39 ++-
+ NEWS         |   24 ++
+ README       |   22 +-
+ afs.c        |    9
+ audioc.c     |   55 ++++-
+ audioc.ggo   |    1
+ audiod.c     |  577 ++++++++++++++++++++++++++++++++++++++++-------------------
+ client.c     |   20 +-
+ client.ggo   |   12 -
+ command.c    |   14 -
+ configure.ac |   64 +++---
+ crypt.c      |    8
+ daemon.c     |    6
+ gui.c        |   52 ++---
+ gui_common.c |   70 -------
+ index.html   |    2
+ mp3.c        |    2
+ mp3dec.c     |    6
+ net.c        |    2
+ ogg.c        |    2
+ oggdec.c     |    8
+ para.h       |    8
+ play.c       |   51 ++---
+ sdl_gui.c    |   53 ++---
+ server.c     |   48 +---
+ server.ggo   |    1
+ server.h     |    2
+ stat.c       |   65 ++++++
+ 28 files changed, 744 insertions(+), 479 deletions(-)
+
+0.2.3 (2005-09-01) "hydrophilic movement"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Two new executables and major feature enhancements.
+
+       o audiod filters: It is now possible to specify arbitrary many
+         (including none) filters for each supported audio
+         format. This can be used e.g. for normalizing volume,
+         transforming or grabbing the audio stream, or for using
+         visualizers.  Read the output of "para_audiod -h" for the
+         syntax of the new --filter_cmd option.
+
+       o new executable: para_play, a tiny alsa player. It
+         can play wave files or raw pcm (16 bit little endian)
+         from stdin.
+
+       o new executable: para_compress, a dynamic range compressor
+         intended to keep audio output at a consistent volume. Derived
+         from AudioCompress, http://trikuare.cx/code/AudioCompress.html.
+
+       o audiod: New option: --stream_delay. This can be used in
+         a local network to syncronize the audio output of all
+         clients that play the same stream.
+
+New files: compress.c compress.ggo play.c play.ggo
+
+ CREDITS             |    2
+ FEATURES            |   31 +--
+ Makefile.in         |   31 ++-
+ NEWS                |   24 ++
+ README              |    8
+ afs.c               |   14 -
+ audiod.c            |  463 +++++++++++++++++++++++++++++++++++++++++-----------
+ audiod.ggo          |    7
+ configure.ac        |   16 +
+ index.html          |   19 ++
+ mysql.c             |   17 +
+ net.c               |    8
+ recv.c              |   42 +++-
+ scripts/demo-script |    2
+ signal.c            |   22 ++
+ stat.c              |    2
+ 16 files changed, 542 insertions(+), 166 deletions(-)
+
+0.2.2 (2005-08-19) "tangential excitation"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Mostly internal changes in this release, but also some new commands
+for the mysql database tool.
+
+       o cleanup exec.c, fix para_exec bug
+       o compile time loglevel (log messages below the given level
+         won't be compiled in, which reduces the size of the
+         resulting binaries)
+       o new log macros that shorten the size of the source code.
+       o workaround a gcc-4.1 bug (?) that caused send_cred_buffer()
+         to send only zeros. With this workaround, para_audioc works
+         again.
+       o avoid gcc-4 warning: conflicting types for built-in function 'clog'
+       o new mysql commands: "rm" (remove entry), "mv" (rename entry) "ne"
+         (new entry), "snp" (set numplayed). Read the manual for more
+         information.
+
+ INSTALL             |   10 -
+ Makefile.in         |    7 -
+ NEWS                |   19 +++
+ README              |    5
+ afs.c               |   75 ++++++--------
+ audioc.c            |    5
+ audiod.c            |  137 ++++++++++++-------------
+ client.c            |   83 +++++++--------
+ command.c           |   85 +++++++---------
+ configure.ac        |    2
+ crypt.c             |    6 -
+ daemon.c            |   26 ++--
+ dbadm.c             |  128 ++++++++++++------------
+ dopey.c             |    9 -
+ exec.c              |  180 ++++++----------------------------
+ fade.c              |   76 +++++++-------
+ gui.c               |   88 ++++++++--------
+ krell.c             |  113 ++++++++++-----------
+ mp3.c               |   49 ++++-----
+ mysql.c             |  275 ++++++++++++++++++++++++++++++++++++++++++++--------
+ net.c               |   44 ++------
+ ogg.c               |   91 ++++++++---------
+ oggdec.c            |    2
+ para.h              |  110 +++++++++++++-------
+ recv.c              |   26 ++--
+ scripts/demo-script |    4
+ sdl_gui.c           |    3
+ send.c              |   26 ++--
+ server.c            |   74 ++++++-------
+ signal.c            |   19 +--
+ skencil/overview.sk |   34 +++---
+ slider.c            |    4
+ stat.c              |   27 ++---
+ string.c            |   62 +++++++----
+ 34 files changed, 1000 insertions(+), 904 deletions(-)
+
+0.2.1 (2005-08-15) "surreal experience"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Here comes paraslash-0.2.1. It contains a couple of new features and,
+surprise, only minor bug fixes.
+
+       o kill noisy mp3 debug message
+       o cleanup of the build system
+       o para_server and para_client directly use the crypto routines
+         of the openssl library rather than invoking the openssl command
+         line utitlity
+       o server/audiod: new option --user to switch to the given user
+         when invoked as root. Read the output of "para_server -h" for
+         more information.
+        o gui/sdl_gui: new option --stat_cmd to be used to retrieve the
+         status. Default: "para_audioc stat"
+       o sdl_gui: new option --pic_cmd to be used to download the picture.
+         Default: "para_client pic"
+       o audiod: 5 slots ought to be enough for everybody
+       o audiod: new status item: Uptime, kill hup command
+
+
+New files: crypt.c crypt.h
+
+ 1.0                 |    2
+ FEATURES            |   18 +++----
+ INSTALL             |   37 ++++++++++-----
+ Makefile.in         |  114 +++++++++++++++++++++---------------------------
+ NEWS                |   38 +++++++++++-----
+ README              |   27 +++++------
+ afs.c               |    6 --
+ audiod.c            |  117 ++++++++++++++++++-------------------------------
+ audiod.ggo          |    1
+ client.c            |   78 +++------------------------------
+ command.c           |  103 +++++++------------------------------------
+ configure.ac        |   78 ++++++++++++++++++++++-----------
+ daemon.c            |   44 +++++++++++++++++-
+ dbadm.c             |    7 +-
+ dopey.c             |   14 ++---
+ fade.c              |    3 -
+ gui.c               |    7 +-
+ gui.ggo             |    3 -
+ gui_common.c        |    7 ++
+ gui_theme.c         |  122 ++++++++++++++++++++++++++++------------------------
+ index.html          |   38 ++++++++++------
+ mp3.c               |   79 +++++++++++++++++----------------
+ mp3dec.c            |    8 +--
+ mysql.c             |   14 ++++-
+ net.c               |    3 -
+ ogg.c               |   21 ++++----
+ para.h              |   11 ++--
+ sdl_gui.c           |   19 ++++----
+ sdl_gui.ggo         |   10 ++--
+ server.c            |   19 --------
+ server.ggo          |    3 -
+ server.h            |    3 -
+ slider.c            |   19 +++-----
+ stat.c              |   24 +++++-----
+ string.c            |   12 ++++-
+ 36 files changed, 530 insertions(+), 581 deletions(-)
+
+
+0.2.0 (2005-08-06) "distributed diffusion"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+After several month of increased development activity, paraslash-0.2.0
+has arrived. It contains many new features and is much more
+self-contained than the old 0.1.x series. Enjoy!
+
+
+       o para_server: fix hang on song change and crash on sighup.
+         Speed up mysql queries. The DIR_LIKE macro is gone.
+       o new executables: para_audiod, the local audio daemon that
+         starts playback (uses SCM_CREDENTIALS socket magic) and
+         para_audioc, the corresponding client.
+       o new executables: para_mp3dec/para_oggdec, two really teensy
+         decoders. para_mp3dec is based on libmad, para_oggdec requires
+         libvorbisfile.
+        o ovsend/ovrecv are capable of streaming ogg as well as mp3, so
+          they are now called para_send and para_recv respectively.
+       o documentation updates
+       o para_gui is themable. For now there is the default theme that
+         looks as before and the simple theme: blue and easy.
+       o gui: audio streaming is now handled by audiod. Time display shows
+         playback time rather than streaming time
+       o slider: update to libzmw-0.2.0
+       o para_krell: fix crash on server shutdown
+       o switch from gzip to bzip2
+
+New files: audioc.c audioc.ggo audiod.c audiod.ggo daemon.c gui_theme.c mp3dec.c oggdec.c
+       recv.c recv.ggo send.c send.ggo stat.c
+
+ 1.0                |    1
+ COPYING            |    2
+ CREDITS            |    4
+ FEATURES           |   24 -
+ INSTALL            |  131 ++++-
+ Makefile.in        |   83 ++-
+ NEWS               |   58 +-
+ PUBLIC_KEY         |   43 +
+ README             |  125 +++--
+ README.mysql       |   18
+ afs.c              |  330 +++++++++-----
+ client.c           |   41 -
+ client.conf.sample |    2
+ client.ggo         |    5
+ command.c          |  350 +++++++--------
+ configure.ac       |   38 +
+ dopey.c            |    2
+ exec.c             |  242 ++++++----
+ fade.c             |   43 -
+ fade.ggo           |    2
+ gui.c              | 1169 +++++++++--------------------------------------------
+ gui.ggo            |   10
+ gui_common.c       |   58 +-
+ index.html         |  176 ++++++-
+ krell.c            |    4
+ mp3.c              |  341 +++++++++------
+ mysql.c            |  180 ++++----
+ net.c              |  161 +++++++
+ ogg.c              |  444 +++++++++++++-------
+ para.h             |  103 ++++
+ sdl_gui.c          |  708 ++++++++++++++------------------
+ sdl_gui.ggo        |    2
+ server.c           |  175 +++----
+ server.ggo         |   13
+ server.h           |   20
+ slider.c           |  160 +++----
+ string.c           |   63 ++
+ time.c             |  103 +++-
+ 38 files changed, 2821 insertions(+), 2613 deletions(-)
+
+
+0.1.7 (2005-04-18) "melting penetration"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+The main change in this release is clearly the oggvorbis rewrite,
+but there are also lots of smaller changes. If you intend to use both
+the mp3 and the ogg plugin, it is recommended to use software mixing,
+e.g. the dmix plugin which is provided by ALSA.
+
+       o new executables: para_ovsend and para_ovrecv for sending/receiving
+         oggvorbis files via rtp. Requires the open rtp library. Get it at
+         http://www.linphone.org/ortp/
+       o rewrite of the ogg_vorbis core code
+        o configure detects libzmw and, if detected, includes
+          para_slider to the list of binaries to be built by make
+       o server stream writers read from their associated fifo rather
+         than from stdin
+       o slider: two new sliders, lastplayed and numplayed
+       o fix nasty double free bug which caused random segfaults in case of
+         mp3 files with invalid header information
+       o gui: new command line option: --stream_timeout=seconds  to
+         deactivate a slot if it is idle for that many seconds (default=`5')
+       o diffstats
+
+New files: ovrecv.c ovrecv.ggo ovsend.c ovsend.ggo time.c
+ CREDITS         |    2
+ FEATURES        |   14 -
+ Makefile.in     |   20 +
+ NEWS            |   25 ++
+ README          |   28 ++
+ README.mysql    |   18 -
+ afs.c           |  238 ++++++++++++++++-------
+ autogen.sh      |    5
+ bash_completion |    2
+ command.c       |    9
+ configure.ac    |  115 ++++++++---
+ exec.c          |    6
+ gui.c           |    8
+ gui.ggo         |    1
+ krell.c         |   14 -
+ mp3.c           |  117 +++++++----
+ mysql.c         |    4
+ ogg.c           |  578 +++++++++++++++++++++-----------------------------------
+ para.h          |    9
+ server.c        |   65 ++++--
+ server.ggo      |    2
+ server.h        |   17 +
+ slider.c        |   68 ++++++
+ string.c        |   11 +
+ 24 files changed, 794 insertions(+), 582 deletions(-)
+
+
+0.1.6 (2005-03-05) "asymptotic balance"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Only little user-visible changes in this release. Mainly bugfixes and
+core code cleanup. This is probably the most stable version ever if you
+stick to mp3...
+
+       o fix several memory leaks
+       o rename default name of mysql database from "music" to "paraslash".
+         Use para_server's  --mysql_database option if you do not want to
+         rename your old database.
+       o rework ogg vorbis code
+       o make update command work on mysql servers with LOCAL_INFILE
+         disabled
+       o gui: improved stream I/O (slots)
+       o simplified audio format API
+       o para_pob_ogg is gone
+
+0.1.5 (2004-12-31) "opaque eternity"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Let's slide gently into the new year.
+
+        o new: para_slider (not built automatically, type "make
+          para_slider" to build). A toy for those who always felt that
+          creating stream definitions is difficult. See screenshots,
+          README and FEATURES for more info.
+       o improved signal handling. Fixes server segfault on SIGHUP
+         for linux kernels newer than Aug 24 2004 and makes para_gui
+         race-free.
+       o reload database tool on SIGHUP
+       o improved help message for sl
+       o do not log "broken pipe" messages as errors. They are
+         perfectly ok.
+       o fix wrong error message on permission errors
+
+0.1.4 (2004-12-19) "tunneling transition"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Bugfix release. As expected, 0.1.3 introduced a bunch of new bugs.
+Hopefully, most of them got wiped out with this release. Some
+enhancements went also in.
+
+       o improved error diagnostics for all commands
+       o stradd/picadd: overwrite previous contents if entry already
+          exists, rather than returning errors
+       o stradd: use current stream if invoked without args
+       o faster (and hopefully more stable) ogg-vorbis handling
+       o para_krell: reap children to avoid zombie-flooding in case
+         no server is running
+       o si: report also server pid
+       o server: don't busy-loop if dbtool reports only invalid files.
+       o gui: CTRL+C works again, fix stream_read command line option
+       o fix pic_add, hist
+       o fix mysql dbtool startup in case no database exists
+       o many small fixes and cleanups
+
+0.1.3: (2004-12-10) "vanishing inertia"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Starting from this release, the database tools are integrated in the
+server binary. This decreases server startup time, reduces code size
+and speeds up database commands. However, the layout of the underlying
+mysql database changed only slightly and 0.1.3 should be backwards
+compatible in that respect.
+
+Visible changes:
+
+       o If mysql is not detected at compile time, or fails to init
+         at runtime, fall back to the dopey database tool which should
+         always work.
+       o para_dbtool and dbtool.conf are gone. All mysql specific
+         options are read from server.conf and are prefixed by 'mysql_'.
+       o new command: cdt (change database tool)
+       o new command line option: dbtool (choose startup database tool)
+       o The name of current stream is now stored in the database,
+         so paraslash remembers its current stream when restarted.
+       o new command: csp (change stream and play)
+       o para_gui also reports current database tool and server uptime
+
+
+0.1.2: (2004-11-28) "spherical fluctuation"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Point release before the big dbtool changes go in.
+
+       o dbtool: rename ca to cam (copy all meta data). It now also
+         copies numplayed and lastplayed time as well as the picture
+         id.
+       o fix endless-loop-bug caused by mp3 files with invalid header
+
+0.1.1: (2004-11-05) "floating atmosphere"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+       o gkrellm plugin
+       o new dbtool command: mbox. Browse your sound-file collection
+         with your favorite mail reader.
+       o several small fixes
+
+0.1.0: (2204-10-22) "rotating cortex"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+       o fix logging bug for loglevel > VERBOSE
+       o fix skip command
+       o correct timings for vbr mp3s
+       o modular audio format support
+       o ogg-vorbis support (experimental)
+       o new server option: autoplay
+
+0.0.99: (2004-07-25) "harmonic deviation"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+       o rename projectname from icc to paraslash (play, archive, rate
+         and stream large audio sets happily)
+       o paraslash is no longer restricted to one particular audio
+         streaming software
+       o new dbtool commands (stradd, strq, strdel) for easy stream
+         managment w/o configuration file. That obsoletes stream_defs
+         file/config option for dbtool.
+       o picadd accepts jpeg data from stdin
+       o new server commands: ps (select previous stream), sc (song change)
+       o new default pictures for sdl_gui
+       o gui: new key_map option for binding commands and internal
+         functions to arbitrary keys, nice help screen, rip out
+         soundcard/linux specific stuff, avoid noise artefacts while jumping,
+         show silly logo on startup
+       o new executables: para_fade for fading volume, para_dbadm for
+         manipulating attributes
+       o cdb adds _all_ tables to mysql database
+       o revised and beautified documentation
+       o sample dbtool rewritten in C
+       o autoconf
+
+0.0.98: (2003-12-26) "incremental smoothness"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+       o kick icecast in favour of poc. That removes some races and reduces
+         core code considerably.
+       o cbr/vbr is displayed by stat and gui/sdl_gui. New status flags
+         give finer info on afs' status.
+        o gui can start decoder (see config options). Further new gui
+         commands: refresh (^L), jmp (F1-F10)
+        o gui rereads conf on SIGUSR1 instead of SIGHUP. SIGHUP
+          terminates gui. This fixes dead instances consuming memory
+          continuously.
+       o new dbtool command: verb for sending verbatim sql queries.
+       o fix pid_list races (by removing pid_list)
+       o codename funnies
+
+0.0.97: (2003-10-26)
+~~~~~~~~~~~~~~~~~~~~
+       o installation prefix now defaults to /usr/local
+       o new commands for gui: snozze, sleep and reread config
+       o config file for gui and sdl_gui
+       o fix problems with filenames containing funny characters
+         (reported by Thomas Forell)
+       o improved signal handling for gui, now it rereads conf on SIGHUP
+       o new dbtool command: cdb (create database)
+       o switch from argtable to gengetopt
+       o major code cleanup and speed improvements
+       o fix several potential buffer overflows
+       o many small fixes and cleanups
+
+0.0.96 (2003-08-30)
+~~~~~~~~~~~~~~~~~~~
+       o easy stream_defs syntax
+       o sdl_gui can display images associated to the file being played
+       o Major feature enhancements for icc_gui including dynamic text
+         placement and the top/bottom window design
+       o vrfy/clean now also checks for NULL values in attributes as
+         well as for invalid picture pointers
+       o fix long outstanding case sensitivity bug
+       o many small fixes and cleanups
+
+0.0.95 (2003-06-29)
+~~~~~~~~~~~~~~~~~~~
+       o sdl gui runs much faster
+       o new dbtool command: ca (copy attributes)
+       o count and display number of times the song has been played
+       o new feature: scoring
+       o command line options for sdl_gui
+       o simpler syntax of streams file
+       o decrease network traffic of stat
+       o fix zombie bug
+       o many small fixes and cleanups
+
+0.0.94 (2003-05-04)
+~~~~~~~~~~~~~~~~~~~
+       o new server command: ns (next stream)
+       o new icc_gui command: c (change stream)
+       o internal mp3info
+       o stat shows also id3 tag info
+       o new sdl based gui
+       o log flodding bug fixed
+       o many small fixes and cleanups
+
+0.0.93 (2003-03-28)
+~~~~~~~~~~~~~~~~~~~
+       o colors for icc_gui
+       o icc_gui sets volume directly (linux only)
+       o proper locking that fixes some races
+       o fix security bug that caused commands to be executed even
+         with unsufficient permissions
+       o new command: hup to make all servers reread their configuration file
+       o icecast meta data streaming
+       o many small fixes and cleanups
diff --git a/PUBLIC_KEY b/PUBLIC_KEY
new file mode 100644 (file)
index 0000000..671e45d
--- /dev/null
@@ -0,0 +1,35 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+mQGiBD7zR2oRBACHWxj9A83mTutuXn4Om8pn/wKyBBfMk7+RiF6tcPiwX+cLYNpv
+BVN2HDk2QQxzInsU0QIC8SnaCL+BE5s14CjlFbyRdMYTusMDDbEulrsoYUzV+Ut4
+LAdNasCvB03sYBqRwrgy7Qj90mRJOMM8k0s8YoZTTfBFH+oZS8BJUy2bgwCgq4Tp
+6pGViIMiZgNC3+xYCOArRPUD/26O3sUErfXH5AOpHv7HKCOS9+Xyx/8Bj7CE6dmz
+4sC+rh9cr+cOl6Ux/qupsgB02ZreWHgRVgSEIufilciJKHMv81N1HB4dbqHr4vNu
+ooavcj3Ffp2FI2bEhhwE3kzVhHopxa6H2AmgQFx9upXZNoRiBPSKnD/OfbUsIm/R
+nzlxA/9wJ5KnP0xdKEHnJT++4c3nlSv4vPl33Oua5nV05g3/MqihdUelLudBSRb4
+bSrNMZF+Fv0zjPsnVrgVdAPbWLPZmL9cJBabEJ6EA3doYNKEJ2u83Dd5S/aTAkH9
+r88xLW51bbtRRJFXdwZx2x/uZQI76M6JuMrzRwhkpbZW70Ar77QfQW5kcmUgTm9s
+bCA8bWFhbkBwYXJhc2xhc2gub3JnPoheBBMRAgAeBQJC5Oz+AhsjBgsJCAcDAgMV
+AgMDFgIBAh4BAheAAAoJEFraNUAxAJMPKZUAn1eNn9/zydyds+WtXYh4p4L75CDq
+AJ4idK9WPfbyH+PQLeVvoMKnJlpqpLQsQW5kcmUgTm9sbCA8bm9sbEBtYXRoZW1h
+dGlrLnR1LWRhcm1zdGFkdC5kZT6IWQQTEQIAGQUCPvNHagQLBwMCAxUCAwMWAgEC
+HgECF4AACgkQWto1QDEAkw9sqgCfaivNblcXj/CReVkMAUlS1GihCYMAn10hlgFY
+dzxtEz7kq0+vHfJx3kQJtCFBbmRyZSBOb2xsIDxtYWFuQHN5c3RlbWxpbnV4Lm9y
+Zz6IYQQTEQIAIQIbIwYLCQgHAwIDFQIDAxYCAQIeAQIXgAUCQuTxGAIZAQAKCRBa
+2jVAMQCTDwqHAJ4uq5RbbAvddMj9SzfPXOjkXZEMEwCffXrKaf6hYas6LPXn0U8A
+E3eKiPS5Ag0EPvNHeRAIAIHxWMwuho61sLeoa0QTnxURcoqC60euJnleMEOQ3bzl
+vw4pHFQjI2eww1sUiBCw1MQ0omR3NE346t4J3aNSAcR/RvzxV+xL30l6jvxldbwB
+7KNFy0fich5L6rh0f3+ZYmwiHCClx0fGCV3HLEk2jHysB7OgzahY0XCMPOXKAoWA
+NCXU3qw/ty/W9TkD/sjv9CixONzuzyMuReeRqG9eLTDIkOA41JxzOGvRqLPbX/tX
+lD6ZVDp5+ceyyG6e4QQ3jXUhm4jFiKhqENwpHxPjK+wQFmdV2RHps8i5Y9JM1a2q
+oVdk/seG+SyTTq3/odCzEwVEsovBMkKNxn2h2wdgZ9sAAwUH+wfdEZQDW7Hj7qG9
+k1Q96iLvAB3d4jRSmVgn0b+HomBp+KGfpsqWI7qTz4x8Tg+F6+C2MrRFxWYGMVLC
+hjM7HH31FMK3MaD5IUHC3/JpsA3S+diUhyM3kqpvyZAlzOv5YdKHG6XDKva8WEgE
+9OrEd5Wp8Gr6fRlX3Odb1eBYGfmSaA3y6Qd8iTOFELSzOxsx5uTIPaviACuVZHAO
+aksQxVdH+6/TNeBNWRQy/8yaLItlL5gxEs1WmjGoIX65g9mTv2PB1PAN0fC4r5En
+vJN3X3DHMlcneaWx2zH0OFYHjInZitAutxAb61EuPBe2W0hYnGzVaZTIN2pfAImm
+xY8TcfOIRgQYEQIABgUCPvNHeQAKCRBa2jVAMQCTDztMAJ9+oOTYuDDl2y8frQ4i
+WKvqFGIO/ACgjtIdZj4ZlBiAYHArJOlyFYOLjWw=
+=5Iez
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..967655f
--- /dev/null
+++ b/README
@@ -0,0 +1,219 @@
+Paraslash README
+================
+Paraslash is an acronym for
+
+       Play, archive, rate and stream large audio sets happily
+
+It contains the following programs:
+
+- para_server (obligatory):
+
+        This server listens on a specified tcp port and accepts the
+        usual commands such as play, stop, pause, next. However, there
+        are many more commands.
+
+       For audio streaming, at least one sender must be activated.
+       At the moment, paraslash contains two internal senders:
+
+       The http sender is recommended for public streams that can
+       be played by any player like mpg123, xmms, winamp...
+
+       The ortp sender is recommended for LAN streaming and for
+       private streams that require authentication.
+
+       It is possible to activate more than one sender simultaneously.
+       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 two database tools
+       available: mysql and dopey. The former is recommended as dopey
+       is only meant as a fallback and as a starting point for people
+       that want to write their own database tool for paraslash.
+
+       The 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.
+
+- para_client (obligatory):
+
+        The client program to connect to para_server.
+
+- para_recv (optional)
+
+       A command line http/ortp stream grabber.
+
+- para_filter (optional)
+
+       An filter program that converts from stdin and writes to
+       stdout. This one is independent from the rest of paraslash,
+       so it might be useful also for different purposes.
+
+       para_filter combines an mp3 decoder an oggvorbis decoder
+       and a volume normalzer. New filters can be added easily due
+       to the modular design. If more than one filter is specified,
+       the given filters are 'piped' together in-memory, i.e. without
+       calling any of the read(2)/write(2)/select(2) etc. functions.
+
+- para_play (optional)
+
+       A small wav/raw player for alsa.
+
+- para_audiod (optional, but recommended):
+
+       The local daemon that starts playback and collects information
+       from para_server to be forwarded to local clients.
+
+       para_audiod reads the audio stream from the network if
+       para_server indicates that there is a stream available. It may
+       be sent through any of the supported filters (see para_filter
+       above) before the result is fed to the output software
+       (default: para_play) which must be capable of reading from
+       stdin, but is not restricted otherwise.
+
+- para_audioc (optional, but recommended)
+
+       A small client that can talk to para_audiod. Used to control
+       para_audiod and to receive status info. It can also be used to
+       grab the stream at any point in the filter chain. para_audioc
+       is needed by para_gui, para_sdl_gui and para_krell, see below.
+
+- 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.
+
+- 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.
+
+- para_krell (optional, only useful in conjunction with the mysql dbtool):
+
+       A plugin for gkrellm which shows small pictures of the
+       current song. It allows you to launch 27 different commands
+       by clicking in different areas of its picture (9 small squares
+       x 3 mouse buttons).
+
+- para_fade (optional):
+
+       A (Linux-only) alarm clock and volume-fader.
+
+- para_dbadm (optional, only useful in conjunction with the mysql dbtool):
+
+       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):
+
+       A small X application which shows a scrollbar for each
+       attribute defined in the mysql database. It creates a stream
+       definition from the values of the scrollbars. This allows
+       to smoothly change the mood of the given stream without any
+       file editing.
+
+- bash_completion (optional):
+
+       A small bash script for inclusion in ~/.bashrc. It gives you
+       command line completion for some paraslash commands.
+
+REQUIREMENTS:
+~~~~~~~~~~~~~
+In any case you need
+
+       - gcc, the gnu compiler collection (shipped with distro): gcc-3
+       or newer is prefered, but gcc-2.95 is still supported. Note
+       that gcc-2.95 may spit out many warnings like unused function
+       parameters and missing initializers. These are all harmless
+       and may be ignored.
+
+       - openssl (needed by server, client): usually shipped with
+       distro, but you might have to install the "development"
+       package as well
+
+               http://www.openssl.org/
+
+       - software mixing, e.g. ALSA and the direct mixing plugin (dmix)
+
+If you want to use the mysql-based dbtool (recommended), you also need
+
+       - mysql-server
+       - mysql-client
+       - libmysqlclient
+
+These are usually shipped with the distro but probably not installed
+by default.
+
+The mp3 decoder of para_filter is based on libmad:
+
+               http://www.underbit.com/products/mad/
+
+If you prefer to use the libmad package provided by your distributor,
+make sure to install the corresponding development package as well.
+
+If you want to stream ogg vorbis files you'll need:
+
+       - libogg, libvorbis, libvorbisfile, and a command line ogg vorbis
+               player, e.g. para_filter or ogg123.
+
+               http://www.xiph.org/downloads/
+
+Note that para_audiod still works even if neither mp3 nor ogg support
+was compiled in. You'll have to use the --no_default_filters option
+in this case (and e.g. "mpg123 -" as the stream write command).
+
+If you intend to use the optional ortp streamer:
+
+       - libortp
+
+               http://www.linphone.org/ortp/
+
+
+For the optional SDL-based gui, the following packages must be installed:
+
+       - X (usually shipped with distro)
+
+               http://www.x.org/
+
+       - libSDL (usually shipped with distro)
+
+               http://www.libsdl.org/index.php
+
+       - SDL_image
+
+               http://www.libsdl.org/projects/SDL_image/
+
+For para_slider, the zero memory widget library is neccessary. Get it at
+
+               http://www710.univ-lyon1.fr/~exco/ZMW/
+
+Finally, para_krell needs
+
+       - gtk2
+
+               http://www.gtk.org/
+
+       - gkrellm2
+
+               http://members.dslextreme.com/users/billw/gkrellm/gkrellm.html
+
+LICENSE:
+~~~~~~~~
+Distribution of paraslash is covered by the GNU GPL. See file COPYING.
+
+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 AUTHOR:
+~~~~~~~~~~~
+Author: Andre Noll <maan@systemlinux.org>
+Comments and bug reports are welcome.
diff --git a/README.mysql b/README.mysql
new file mode 100644 (file)
index 0000000..d2a27e7
--- /dev/null
@@ -0,0 +1,325 @@
+README.mysql
+============
+
+This file describes how to use the mysql database tool 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.
+
+First of all, make sure that
+
+       - mysqld is running
+       - para_server is running and compiled with mysql support (type
+         "para_client si" to find out)
+       - the user who runs para_client has the paraslash DB_WRITE and DB_READ
+         permissions set in server.users
+       - the user who runs para_server has create privileges on the mysql
+         server.
+
+Remember: If something doesn't work as expected, look at the server log file
+and/or increase output verbosity by using the -l switch for server and client.
+
+
+Specify mysql data (port, passwd,...)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Type
+
+       para_server -h
+
+and look at the mysql options. You may either specify these options
+in ~/.paraslash/server.conf or directly at the command line (not
+recommended for passwd option). Don't forget to do
+
+       chmod 600 ~/.paraslash/server.conf
+
+as this file contains the mysql passwd. To make these changes take
+effect you'll need to do
+
+       para_client hup
+
+Or, restart the server.
+
+Switch to the mysql dbtool
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+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
+
+       para_client cdt
+
+which prints the name of the current database tool. If the dopey
+dbtool is still selected, try
+
+       para_client cdt 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
+
+       para_client si
+
+to find out. If mysql is not mentioned as a supported database tool,
+you'll have to recompile.
+
+
+Create a new database
+~~~~~~~~~~~~~~~~~~~~~
+
+Once the mysql database tool is selected, create the database:
+
+       para_client cdb
+       para_client cdt mysql
+
+The second command forces para_server to re-init the mysql dbtool.
+Check the log. There should not be any warnings or errors.
+
+
+Fill your database with content
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+       para_client upd
+
+If this command fails, it most likely means the audio file directory
+(given in the server configuration file) does not exist, is empty,
+not readable, or contains different files with identical basenames. Fix
+this problem before proceeding.
+
+The command
+
+       para_client ls
+
+prints the list of all files known by the mysql dbtool. If the list
+is empty, double check the mysql_audio_file_dir option.
+
+
+Create a stream which selects all songs
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To keep it simple, let's only define the stream "all_songs". See below for
+advanced stream usage.
+
+       para_client stradd all_songs < /dev/null
+       para_client sl 10 all_songs
+
+The latter command should show you ten filenames.
+
+
+Change to the all_songs stream
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+       para_client cs all_songs
+
+You should now be able to start streaming with
+
+       para_client play
+
+
+Attribute usage
+~~~~~~~~~~~~~~~
+
+An attribute is simply a bit which can be set for each sound file
+individually. You may have as many attributes as you like. A new
+attribute "test" is created by
+
+       para_client na test
+
+and
+       para_client laa
+
+lists all available attributes. You can set the "test" attribute for
+the current song by executing
+
+       para_client sa test+
+
+or for any particular song by
+
+       para_client sa test+ filename
+
+You can unset the attribute "test" for the current song with
+
+        para_client sa test-
+
+and you can 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).
+
+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
+temporary file, say tmpfile. An accept/deny/score line consists of
+an identifier ("accept:", "deny:", or "score:"), followed by an
+expression. The command
+
+       para_client stradd my_stream < tmpfile
+
+adds the stream "my_stream" to dbtool's stream database.
+
+If the stream definition is really short, you may also just pipe it to
+the client rather than using temporary files. Like this:
+
+       echo "$MYSTREAMDEF" | para_client stradd my_stream
+
+
+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
+
+               echo 'accept: IS_SET(test)' | para_client stradd only_test
+
+       Then, after switching to the "only_test" stream with
+
+               para_client cs 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:
+
+       para_client strq only_test
+
+The accept/deny expressions are used to find out which songs are
+permitted. The following four cases are all possible and valid:
+
+       o Neither accept nor deny lines: This selects all songs.
+
+       o Only accept lines: Songs that match at least one accept
+       expression are accepted, all others are denied:
+
+               accept_expr1 or accept_expr2 or ...
+
+       o Only deny lines: Songs that match at least one deny expression are
+       denied, all others are accepted:
+
+               not (deny_expr1 or deny_expr2 ...)
+
+       o Both accept and deny lines: A song is accepted if it matches
+       at least one accept expression, but no deny expression, i.e.
+
+               (accept_expr1 or accept_expr2 or ..) and not
+                       (deny_expr1 or deny_expr2 ..)
+
+The command
+
+       para_client streams
+
+lists all available streams and
+
+       para_client strdel streamname
+
+removes the stream "streamname".
+
+There are more sophisticated ways to define a stream than just using
+one IS_SET() macro as in the example above. Of course, IS_SET(foo)
+is true for a given audio file if and only if it has the attribute
+"foo" set.  Here are some more macros you can use:
+
+       o IS_N_SET(attr): True if attribute attr is not set
+
+       o NAME_LIKE(string): True if basename is like (in the sense
+       of mysql) "string"
+
+       o LASTPLAYED(): Expands to number of minutes that are gone
+       since this audio file has been played (by paraslash).
+
+       o NUMPLAYED(): Expands to number of times, the file has
+       been played.
+
+       o PICID(): Expands to the number of the picture which is
+       associated with this song.
+
+To give a real-life example, suppose you have already added the
+attributes "pop", "rock" with the "na" command. Assume also that you
+have set these attributes for some or all of your songs having the
+corresponding properties.
+
+If you like to be waked up in the morning by poprock songs, but you
+have some strange feeling telling you that just a few seconds of
+Madonna's voice will be enough to mess up your whole day, just write
+the lines
+
+       accept: IS_SET(pop) and IS_SET(rock)
+       deny: NAME_LIKE(%Madonna%)
+
+to some temporary file "tmp" and do
+
+       para_client stradd wake < tmp
+
+You can then switch to the new stream with
+
+       para_client cs wake
+
+or you can let cron do this for you on a daily basis..
+
+Accept/deny lines affect only the set of admissible songs, but not
+the order in which these songs are played. That's where the score
+expression comes into play.
+
+Scoring
+~~~~~~~
+You may put a single score line anywhere in the stream definition. If
+omitted, the default scoring rule specified in the configuration file
+applies. If there is no default scoring rule in the config file either,
+the compiled in default is going to be used (see para_server -h).
+
+Simple examples of scoring rules (either specified in a stream
+definition or as the default scoring rule in the config file) include:
+
+       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.
+
+However, one disadvantage of this scoring sheme is that new songs,
+once played, are going to be deferred for a possibly very long period
+depending on the size of your collection of (admissible) songs. Hence
+the following scoring rule comes into mind:
+
+        score: -NUMPLAYED()
+
+since this gives newer songs, i.e. songs 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()
+
+which subtracts 10 score points for each time paraslash has played
+this song.
+
+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
+
+       para_client pic > filename
+
+in order to feed it to your favorite tool. Try
+
+       para_client help | grep ^pic
+
+and read the online help of the shown commands for more information.
diff --git a/SFont.c b/SFont.c
new file mode 100644 (file)
index 0000000..407ef99
--- /dev/null
+++ b/SFont.c
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) Karl Bartel <karlb@gmx.net> WWW: http://www.linux-games.com
+ *
+ *     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.
+ */
+
+/*Modified 2003 by Andre Noll */
+
+#include <SDL/SDL.h>
+#include "SFont.h"
+#include <stdlib.h> /* exit */
+#include <string.h> /* strlen */
+SFont_FontInfo InternalFont;
+
+Uint32 GetPixel(SDL_Surface * Surface, Sint32 X, Sint32 Y)
+{
+
+       Uint8 *bits;
+       Uint32 Bpp;
+
+       if (X < 0)
+               puts("SFONT ERROR: x too small in GetPixel. Report this "
+                       "to <karlb@gmx.net>");
+       if (X >= Surface->w)
+               puts("SFONT ERROR: x too big in GetPixel. Report this to "
+                       "<karlb@gmx.net>");
+       Bpp = Surface->format->BytesPerPixel;
+       bits = ((Uint8 *) Surface->pixels) + Y * Surface->pitch + X * Bpp;
+
+       /* Get the pixel */
+       switch (Bpp) {
+       case 1:
+               return *((Uint8 *) Surface->pixels + Y * Surface->pitch + X);
+               break;
+       case 2:
+               return *((Uint16 *) Surface->pixels + Y * Surface->pitch / 2 +
+                        X);
+               break;
+       case 3:{        /* Format/endian independent  */
+                       Uint8 r, g, b;
+                       r = *((bits) + Surface->format->Rshift / 8);
+                       g = *((bits) + Surface->format->Gshift / 8);
+                       b = *((bits) + Surface->format->Bshift / 8);
+                       return SDL_MapRGB(Surface->format, r, g, b);
+               }
+               break;
+       case 4:
+               return *((Uint32 *) Surface->pixels + Y * Surface->pitch / 4 +
+                        X);
+               break;
+       }
+
+       return -1;
+}
+
+void InitFont2(SFont_FontInfo * Font)
+{
+       int x = 0, i = 0;
+
+       if (!Font->Surface) {
+               printf("The font has not been loaded!\n");
+               exit(EXIT_FAILURE);
+       }
+
+       if (SDL_MUSTLOCK(Font->Surface))
+               SDL_LockSurface(Font->Surface);
+
+       while (x < Font->Surface->w) {
+               if (GetPixel(Font->Surface, x, 0) ==
+                   SDL_MapRGB(Font->Surface->format, 255, 0, 255)) {
+                       Font->CharPos[i++] = x;
+                       while ((x < Font->Surface->w - 1)
+                              && (GetPixel(Font->Surface, x, 0) ==
+                                  SDL_MapRGB(Font->Surface->format, 255, 0,
+                                             255)))
+                               x++;
+                       Font->CharPos[i++] = x;
+               }
+               x++;
+       }
+       if (SDL_MUSTLOCK(Font->Surface))
+               SDL_UnlockSurface(Font->Surface);
+
+       Font->h = Font->Surface->h;
+       SDL_SetColorKey(Font->Surface, SDL_SRCCOLORKEY,
+                       GetPixel(Font->Surface, 0, Font->Surface->h - 1));
+}
+
+#if 0
+void InitFont(SDL_Surface * Font)
+{
+       InternalFont.Surface = Font;
+       InitFont2(&InternalFont);
+}
+
+#endif
+
+
+
+void PutString2(SDL_Surface * Surface, SFont_FontInfo * Font, int x, int y,
+               char *text)
+{
+       int ofs;
+       int i = 0;
+       SDL_Rect srcrect, dstrect;
+
+       while (text[i] != '\0') {
+               if (text[i] == ' ') {
+                       x += Font->CharPos[2] - Font->CharPos[1];
+                       i++;
+               } else {
+                       ofs = ((unsigned char) text[i] - 33) * 2 + 1;
+                       srcrect.w = dstrect.w = (Font->CharPos[ofs + 2]
+                               + Font->CharPos[ofs + 1]) / 2
+                               - (Font->CharPos[ofs] + Font->CharPos[ofs - 1])
+                                        / 2;
+                       srcrect.h = dstrect.h = Font->Surface->h - 1;
+                       srcrect.x =
+                           (Font->CharPos[ofs] + Font->CharPos[ofs - 1]) / 2;
+                       srcrect.y = 1;
+                       dstrect.x =
+                           x - (float) (Font->CharPos[ofs] -
+                                        Font->CharPos[ofs - 1]) / 2;
+                       dstrect.y = y;
+                       SDL_BlitSurface(Font->Surface, &srcrect, Surface,
+                                       &dstrect);
+                       x += Font->CharPos[ofs + 1] - Font->CharPos[ofs];
+                       i++;
+               }
+       }
+}
+#if 0
+void PutString(SDL_Surface * Surface, int x, int y, char *text)
+{
+       PutString2(Surface, &InternalFont, x, y, text);
+}
+
+#endif
+int TextWidth2(SFont_FontInfo * Font, char *text)
+{
+       int ofs = 0;
+       int i = 0, x = 0;
+
+       while (text[i] != '\0') {
+               if (text[i] == ' ') {
+                       x += Font->CharPos[2] - Font->CharPos[1];
+                       i++;
+               } else {
+                       ofs = ((unsigned char) text[i] - 33) * 2 + 1;
+                       x += Font->CharPos[ofs + 1] - Font->CharPos[ofs];
+                       i++;
+               }
+       }
+       return x;
+}
+
+#if 0
+int TextWidth(char *text)
+{
+       return TextWidth2(&InternalFont, text);
+}
+#endif
+
+void XCenteredString2(SDL_Surface * Surface, SFont_FontInfo * Font, int y,
+                     char *text)
+{
+       PutString2(Surface, Font, Surface->w / 2 - TextWidth2(Font, text) / 2,
+                  y, text);
+}
+
+#if 0
+void XCenteredString(SDL_Surface * Surface, int y, char *text)
+{
+       XCenteredString2(Surface, &InternalFont, y, text);
+}
+#endif
+
+void SFont_InternalInput(SDL_Surface * Dest, SFont_FontInfo * Font, int x,
+                        int y, int PixelWidth, char *text)
+{
+       SDL_Event event;
+       int ch = -1, blink = 0;
+       long blinktimer = 0;
+       SDL_Surface *Back;
+       SDL_Rect rect;
+       int previous;
+
+       Back = SDL_AllocSurface(Dest->flags,
+               Dest->w,
+               Font->h,
+               Dest->format->BitsPerPixel,
+               Dest->format->Rmask,
+               Dest->format->Gmask, Dest->format->Bmask, 0);
+       rect.x = 0;
+       rect.y = y;
+       rect.w = Dest->w;
+       rect.h = Font->Surface->h;
+       SDL_BlitSurface(Dest, &rect, Back, NULL);
+       PutString2(Dest, Font, x, y, text);
+       SDL_UpdateRects(Dest, 1, &rect);
+
+       /* start input */
+       previous = SDL_EnableUNICODE(1);
+       blinktimer = SDL_GetTicks();
+       while (ch != SDLK_RETURN) {
+               if (event.type == SDL_KEYDOWN) {
+                       ch = event.key.keysym.unicode;
+                       if (((ch > 31) || (ch == '\b')) && (ch < 128)) {
+                               if ((ch == '\b') && (strlen(text) > 0))
+                                       text[strlen(text) - 1] = '\0';
+                               else if (ch != '\b')
+                                       sprintf(text, "%s%c", text, ch);
+                               if (TextWidth2(Font, text) > PixelWidth)
+                                       text[strlen(text) - 1] = '\0';
+                               SDL_BlitSurface(Back, NULL, Dest, &rect);
+                               PutString2(Dest, Font, x, y, text);
+                               SDL_UpdateRects(Dest, 1, &rect);
+                               SDL_WaitEvent(&event);
+                       }
+               }
+               if (SDL_GetTicks() > blinktimer) {
+                       blink = 1 - blink;
+                       blinktimer = SDL_GetTicks() + 500;
+                       if (blink) {
+                               PutString2(Dest, Font,
+                                          x + TextWidth2(Font, text), y, "|");
+                               SDL_UpdateRects(Dest, 1, &rect);
+                       } else {
+                               SDL_BlitSurface(Back, NULL, Dest, &rect);
+                               PutString2(Dest, Font, x, y, text);
+                               SDL_UpdateRects(Dest, 1, &rect);
+                       }
+               }
+               SDL_Delay(1);
+               SDL_PollEvent(&event);
+       }
+       text[strlen(text)] = '\0';
+       SDL_FreeSurface(Back);
+       /* restore the previous state */
+       SDL_EnableUNICODE(previous);
+}
+
+void SFont_Input2(SDL_Surface * Dest, SFont_FontInfo * Font, int x, int y,
+                 int PixelWidth, char *text)
+{
+       SFont_InternalInput(Dest, Font, x, y, PixelWidth, text);
+}
diff --git a/SFont.h b/SFont.h
new file mode 100644 (file)
index 0000000..acdae2a
--- /dev/null
+++ b/SFont.h
@@ -0,0 +1,59 @@
+/************************************************************************ 
+*    SFONT - SDL Font Library by Karl Bartel <karlb@gmx.net>           *
+*                                                                       *
+*  All functions are explained below. There are two versions of each    *
+*  funtction. The first is the normal one, the function with the        *
+*  2 at the end can be used when you want to handle more than one font  *
+*  in your program.                                                     *
+*                                                                       *
+************************************************************************/
+
+#ifndef SFONT_H
+#define SFONT_H
+
+#include <SDL/SDL.h>
+
+#ifdef __cplusplus 
+extern "C" {
+#endif
+
+// Delcare one variable of this type for each font you are using.
+// To load the fonts, load the font image into YourFont->Surface
+// and call InitFont( YourFont );
+typedef struct {
+       SDL_Surface *Surface;   
+       int CharPos[512];
+       int h;
+} SFont_FontInfo;
+
+// Initializes the font
+// Font: this contains the suface with the font.
+//       The font must be loaded before using this function.
+void InitFont (SDL_Surface *Font);
+void InitFont2(SFont_FontInfo *Font);
+
+// Blits a string to a surface
+// Destination: the suface you want to blit to
+// text: a string containing the text you want to blit.
+void PutString (SDL_Surface *Surface, int x, int y, char *text);
+void PutString2(SDL_Surface *Surface, SFont_FontInfo *Font, int x, int y, char *text);
+
+// Returns the width of "text" in pixels
+int TextWidth(char *text);
+int TextWidth2(SFont_FontInfo *Font, char *text);
+
+// Blits a string to with centered x position
+void XCenteredString (SDL_Surface *Surface, int y, char *text);
+void XCenteredString2(SDL_Surface *Surface, SFont_FontInfo *Font, int y, char *text);
+
+// Allows the user to enter text
+// Width: What is the maximum width of the text (in pixels)
+// text: This string contains the text which was entered by the user
+void SFont_Input ( SDL_Surface *Destination, int x, int y, int Width, char *text);
+void SFont_Input2( SDL_Surface *Destination, SFont_FontInfo *Font, int x, int y, int Width, char *text);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SFONT_H */
diff --git a/afs.c b/afs.c
new file mode 100644 (file)
index 0000000..cb6c388
--- /dev/null
+++ b/afs.c
@@ -0,0 +1,479 @@
+/*
+ * Copyright (C) 1997-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 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.
+ */
+
+#include <sys/time.h> /* gettimeofday */
+#include "server.cmdline.h"
+#include "db.h"
+#include "server.h"
+#include "afs.h"
+#include "send.h"
+#include "error.h"
+#include "string.h"
+
+extern const char *status_item_list[];
+
+static struct timeval announce_tv;
+static struct timeval data_send_barrier;
+static struct timeval eof_barrier;
+
+extern struct misc_meta_data *mmd;
+extern struct dbtool dblist[];
+extern struct sender senders[];
+extern struct gengetopt_args_info conf;
+
+static FILE *audio_file = NULL;
+
+#if 1
+       void mp3_init(void *);
+#endif
+
+#ifdef HAVE_OGGVORBIS
+       void ogg_init(void *);
+#endif
+
+/**
+ * the list of supported  audio formats
+ */
+struct audio_format afl[] = {
+#if 1
+       {
+               .name = "mp3",
+               .init = mp3_init,
+       },
+#endif
+#ifdef HAVE_OGGVORBIS
+       {
+               .name = "ogg",
+               .init = ogg_init,
+       },
+#endif
+       {
+               .name = NULL,
+       }
+};
+
+
+/**
+ * check if audio file sender is playing
+ *
+ * \return greater than zero if playing, zero otherwise.
+ *
+ */
+unsigned int afs_playing(void)
+{
+       return mmd->new_afs_status_flags & AFS_PLAYING;
+}
+
+/**
+ * check if 'next' flag is set afs_status_flags
+ *
+ * \return greater than zero if set, zero if not.
+ *
+ */
+unsigned int afs_next(void)
+{
+       return mmd->new_afs_status_flags & AFS_NEXT;
+}
+
+/**
+ * check if a reposition request is pending
+ *
+ * \return greater than zero if true, zero otherwise.
+ *
+ */
+unsigned int afs_repos(void)
+{
+       return mmd->new_afs_status_flags & AFS_REPOS;
+}
+
+/**
+ * check if audio file sender is paused
+ *
+ * \return greater than zero if paused, zero otherwise.
+ *
+ */
+unsigned int afs_paused(void)
+{
+       return !(mmd->new_afs_status_flags & AFS_NEXT)
+               && !(mmd->new_afs_status_flags & AFS_PLAYING);
+}
+
+/**
+ * get the name of the given audio format
+ * \param i the audio format number
+ *
+ * This returns a pointer to statically allocated memory so it
+ * must not be freed by the caller.
+ */
+const char *audio_format_name(int i)
+{
+       return i >= 0?  afl[i].name : "(none)";
+}
+
+/**
+ * initialize the audio file sender
+ *
+ * Call the init functions of all supported audio format handlers and
+ * initialize all supported senders.
+ */
+void afs_init(void)
+{
+       int i;
+       char *hn = para_hostname(), *home = para_homedir();
+
+       PARA_DEBUG_LOG("supported audio formats: %s\n",
+               SUPPORTED_AUDIO_FORMATS);
+       for (i = 0; afl[i].name; i++) {
+               PARA_NOTICE_LOG("initializing %s handler\n",
+                       afl[i].name);
+               afl[i].init(&afl[i]);
+       }
+       ms2tv(conf.announce_time_arg, &announce_tv);
+       PARA_INFO_LOG("announce timeval: %lu:%lu\n", announce_tv.tv_sec, announce_tv.tv_usec);
+       for (i = 0; senders[i].name; i++) {
+               PARA_NOTICE_LOG("initializing %s sender\n", senders[i].name);
+               senders[i].init(&senders[i]);
+       }
+       free(hn);
+       free(home);
+}
+
+static int get_file_info(int i)
+{
+       return  afl[i].get_file_info(audio_file, mmd->audio_file_info,
+               &mmd->chunks_total, &mmd->seconds_total);
+}
+
+/*
+ * guess the audio format judging from filename
+ * \param name the filename
+ *
+ * \return This function returns -1 if it has no idea what kind of audio
+ * file this might be. Otherwise the (non-negative) number of the audio format
+ * is returned.
+ */
+static int guess_audio_format(const char *name)
+{
+
+       int i, len1 = strlen(name), len2;
+
+       for (i = 0; afl[i].name; i++) {
+               len2 = strlen(afl[i].name);
+               if (len1 < len2)
+                       continue;
+               if (!strncasecmp(name + (len1 - len2), afl[i].name, len2)) {
+                       PARA_DEBUG_LOG("might be %s\n", afl[i].name);
+                       return i;
+               }
+       }
+       return -1;
+}
+
+static int get_audio_format(int omit)
+{
+       int i;
+
+       for (i = 0; afl[i].name; i++) {
+               if (i == omit || !afl[i].get_file_info)
+                       continue;
+               rewind(audio_file);
+               if (get_file_info(i) > 0)
+                       return i;
+               rewind(audio_file);
+       }
+       return -E_AUDIO_FORMAT;
+}
+
+/*
+ * upddate shared mem
+ */
+static int update_mmd(void)
+{
+       int i;
+       struct stat file_status;
+
+       i = guess_audio_format(mmd->filename);
+       if (i < 0 || get_file_info(i) < 0)
+               i = get_audio_format(i);
+       if (i < 0)
+               return i;
+       mmd->audio_format = i;
+       mmd->chunks_sent = 0;
+       mmd->current_chunk = 0;
+       mmd->offset = 0;
+       if (fstat(fileno(audio_file), &file_status) == -1)
+               return -E_FSTAT;
+       mmd->size = file_status.st_size;
+       mmd->mtime = file_status.st_mtime;
+       mmd->events++;
+       PARA_NOTICE_LOG("next audio file: %s\n", mmd->filename);
+       return 1;
+}
+
+static void get_song(void)
+{
+       char **sl = dblist[mmd->dbt_num].get_audio_file_list(10);
+       int i;
+
+       if (!sl)
+               goto err_out;
+       for (i = 0; sl[i]; i++) {
+               struct timeval now;
+               PARA_INFO_LOG("trying %s\n", sl[i]);
+               if (strlen(sl[i]) >= _POSIX_PATH_MAX)
+                       continue;
+               audio_file = fopen(sl[i], "r");
+               if (!audio_file)
+                       continue;
+               strcpy(mmd->filename, sl[i]);
+               if (update_mmd() < 0) {
+                       fclose(audio_file);
+                       audio_file = NULL;
+                       continue;
+               }
+               mmd->num_played++;
+               if (dblist[mmd->dbt_num].update_audio_file)
+                       dblist[mmd->dbt_num].update_audio_file(sl[i]);
+               PARA_DEBUG_LOG("%s", "success\n");
+               mmd->new_afs_status_flags &= (~AFS_NEXT);
+               gettimeofday(&now, NULL);
+               tv_add(&now, &announce_tv, &data_send_barrier);
+
+               goto free;
+       }
+       PARA_ERROR_LOG("%s", "no valid files found\n");
+err_out:
+       mmd->new_afs_status_flags = AFS_NEXT;
+free:
+       if (sl) {
+               for (i = 0; sl[i]; i++)
+                       free(sl[i]);
+               free(sl);
+       }
+}
+
+static int chk_barrier(const char *bname, const struct timeval *now,
+               const struct timeval *barrier, struct timeval *diff, int log)
+{
+       long ms;
+
+       if (tv_diff(now, barrier, diff) > 0)
+               return 1;
+       ms = tv2ms(diff);
+       if (log && ms)
+               PARA_DEBUG_LOG("%s barrier: %lims left\n", bname, ms);
+       return -1;
+}
+
+static void afs_next_chunk_time(struct timeval *due)
+{
+       struct timeval tmp;
+
+       tv_scale(mmd->chunks_sent, &afl[mmd->audio_format].chunk_tv, &tmp);
+       tv_add(&tmp, &mmd->stream_start, due);
+}
+
+/*
+ * != NULL: timeout for next chunk
+ * NULL: nothing to do
+ */
+static struct timeval *afs_compute_timeout(void)
+{
+       static struct timeval the_timeout;
+       struct timeval now, next_chunk;
+
+       if (afs_next() && mmd->audio_format >= 0) {
+               /* only sleep a bit, nec*/
+               the_timeout.tv_sec = 0;
+               the_timeout.tv_usec = 100;
+               return &the_timeout;
+       }
+       gettimeofday(&now, NULL);
+       if (chk_barrier("eof", &now, &eof_barrier, &the_timeout, 1) < 0)
+               return &the_timeout;
+       if (chk_barrier("data send", &now, &data_send_barrier,
+                        &the_timeout, 1) < 0)
+               return &the_timeout;
+       if (mmd->audio_format < 0 || !afs_playing() || !audio_file)
+               return NULL;
+       afs_next_chunk_time(&next_chunk);
+       if (chk_barrier(afl[mmd->audio_format].name, &now, &next_chunk,
+                       &the_timeout, 0) < 0)
+               return &the_timeout;
+       /* chunk is due or bof */
+       the_timeout.tv_sec = 0;
+       the_timeout.tv_usec = 0;
+       return &the_timeout;
+}
+
+static void afs_eof(struct audio_format *af)
+{
+       struct timeval now;
+       int i;
+       char *tmp;
+
+       if (!af || !audio_file) {
+               for (i = 0; senders[i].name; i++)
+                       senders[i].shutdown_clients();
+               return;
+       }
+       gettimeofday(&now, NULL);
+       tv_add(&af->eof_tv, &now, &eof_barrier);
+       af->close_audio_file();
+       audio_file = NULL;
+       mmd->audio_format = -1;
+       af = NULL;
+       mmd->chunks_sent = 0;
+       mmd->offset = 0;
+       mmd->seconds_total = 0;
+       tmp  = make_message("%s:\n%s:\n%s:\n", status_item_list[SI_AUDIO_INFO1],
+               status_item_list[SI_AUDIO_INFO2], status_item_list[SI_AUDIO_INFO3]);
+       strcpy(mmd->audio_file_info, tmp);
+       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);
+       free(tmp);
+       mmd->filename[0] = '\0';
+       mmd->size = 0;
+       mmd->events++;
+}
+
+/**
+ * compute the timeout for para_server's main select-loop
+ *
+ * This function gets called from para_server to determine the timeout value
+ * for its main select loop.
+ *
+ * Before the timeout is computed, the current afs status flags are evaluated
+ * and acted upon by calling appropriate functions from the lower layers.
+ * Possible actions include
+ *
+ *     - request a new file list from the current dabase tool (audio file change)
+ *     - shutdown of all senders (stop/pause command)
+ *     - repositioning of the stream (ff/jmp command)
+ *
+ * \return A pointer to a struct timeval containing the timeout for the next
+ * chunk of data to be sent, or NULL if we're not sending right now.
+ */
+struct timeval *afs_preselect(void)
+{
+       struct audio_format *af = NULL;
+       int i, format;
+       struct timeval *ret;
+again:
+       format = mmd->audio_format;
+       if (format >= 0)
+               af = afl + format;
+       else
+               for (i = 0; senders[i].name; i++)
+                       senders[i].shutdown_clients();
+       if (afs_next() && af) {
+               afs_eof(af);
+               return afs_compute_timeout();
+       }
+       if (afs_paused() || afs_repos()) {
+               for (i = 0; senders[i].name; i++)
+                       senders[i].shutdown_clients();
+               if (af) {
+                       struct timeval now;
+                       if (!afs_paused() || mmd->chunks_sent) {
+                               gettimeofday(&now, NULL);
+                               tv_add(&af->eof_tv, &now, &eof_barrier);
+                       }
+                       if (afs_repos())
+                               tv_add(&now, &announce_tv, &data_send_barrier);
+                       if (mmd->new_afs_status_flags & AFS_NOMORE)
+                               mmd->new_afs_status_flags = AFS_NEXT;
+               }
+               mmd->chunks_sent = 0;
+       }
+       if (af && afs_repos() && mmd->current_chunk != mmd->repos_request)
+               af->reposition_stream(mmd->repos_request);
+       if (afs_repos()) {
+               mmd->new_afs_status_flags &= ~(AFS_REPOS);
+               mmd->current_chunk = mmd->repos_request;
+       }
+       ret = afs_compute_timeout(); 
+       if (!ret && !audio_file && afs_playing() &&
+                       !(mmd->new_afs_status_flags & AFS_NOMORE)) {
+               PARA_DEBUG_LOG("%s", "ready and playing, but no audio file\n");
+               get_song();
+               goto again;
+       }
+       return ret;
+}
+
+/**
+ * afs_send_chunk - paraslash's main sending function
+ *
+ * This function gets called from para_server as soon as the next chunk of
+ * data should be pushed out. It first calls the read_chunk() function of
+ * the current audio format handler to obtain a pointer to the data to be
+ * sent out as well as its length. This information is then passed to each
+ * supported sender's send() function which does the actual sending.
+ *
+ * Return value: Positive return value on success, zero on eof and negative
+ * on errors.
+ */
+
+void afs_send_chunk(void)
+{
+       int i;
+       struct audio_format *af;
+       char *buf;
+       ssize_t ret;
+       struct timeval now, due;
+
+       if (mmd->audio_format < 0 || !audio_file || !afs_playing())
+               return;
+       af = &afl[mmd->audio_format];
+       gettimeofday(&now, NULL);
+       afs_next_chunk_time(&due);
+       if (tv_diff(&due, &now, NULL) > 0)
+               return;
+       buf = af->read_chunk(mmd->current_chunk, &ret);
+       mmd->new_afs_status_flags &= ~(AFS_NEXT | AFS_REPOS);
+       if (!buf) {
+               if (ret < 0)
+                       mmd->new_afs_status_flags = AFS_NEXT;
+               else
+                       mmd->new_afs_status_flags |= AFS_NEXT;
+               afs_eof(af);
+               return;
+       }
+       if (!mmd->chunks_sent) {
+               struct timeval tmp;
+               gettimeofday(&mmd->stream_start, NULL);
+               tv_scale(mmd->current_chunk, &af->chunk_tv, &tmp);
+               mmd->offset = tv2ms(&tmp);
+               mmd->events++;
+       }
+       for (i = 0; senders[i].name; i++)
+               senders[i].send(af, mmd->current_chunk,
+                       mmd->chunks_sent, buf, ret);
+       mmd->new_afs_status_flags |= AFS_PLAYING;
+       mmd->chunks_sent++;
+       mmd->current_chunk++;
+}
diff --git a/afs.h b/afs.h
new file mode 100644 (file)
index 0000000..f1dc9b9
--- /dev/null
+++ b/afs.h
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2005-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 afs.h functions and structures for audio format handling (para_server) */
+
+/** \cond */
+#ifdef HAVE_OGGVORBIS
+#define OV_AUDIO_FORMAT " ogg"
+#define OV_AUDIO_FORMAT_ARRAY , "ogg"
+#else
+#define OV_AUDIO_FORMAT ""
+#define OV_AUDIO_FORMAT_ARRAY
+#endif
+
+#define SUPPORTED_AUDIO_FORMATS "mp3" OV_AUDIO_FORMAT
+#define SUPPORTED_AUDIO_FORMATS_ARRAY "mp3" OV_AUDIO_FORMAT_ARRAY, NULL
+
+
+/* status flags */
+#define AFS_NOMORE 1
+#define AFS_NEXT 2
+#define AFS_REPOS 4
+#define AFS_PLAYING 8
+#define DBT_CHANGE 16
+/** \endcond */
+
+/**
+ * structure for audio format handling
+ *
+ * There's exactly one such struct for each supported audio format. Initially,
+ * only \a name and \a init are defined. During the startup process,
+ * para_server calls the \a init function of each audio format handler which is
+ * expected to fill in all the other function pointers.
+ */
+struct audio_format {
+/**
+ *
+ *
+ * name of the audio format
+ */
+const char *name;
+/**
+ *
+ *
+ * pointer to the audio format handler's init function
+ *
+ * Must initialize all function pointers and is assumed to succeed.
+ */
+void (*init)(void*);
+/**
+ *
+ *
+ * period of time between sending data chunks
+*/
+struct timeval chunk_tv; /* length of one chunk of data */
+/**
+ *
+ *
+ * end of file timeout - do not load new audio file until this time
+ *
+*/
+struct timeval eof_tv; /* timeout on eof */
+/**
+ *
+ *
+ * Pointer to the optional get-header function.
+ *
+ * This is called from a sender in case a new client connects in the middle of
+ * the stream.  The audio format handler may set this to NULL to indicate that
+ * this audio format does not need any special header treatment.  If non-NULL,
+ * the function it points to must return a pointer to a buffer holding the
+ * current audio file header, together with the header length.
+*/
+char *(*get_header_info)(int *header_len);
+/**
+ *
+ *
+ * check if this audio format handler can handle the file
+ *
+ * This is a  pointer to a function returning whether a given file is valid for
+ * this audio format. A negative return value indicates that this audio format
+ * handler did not recognize the given file. On success, the function is
+ * expected to return a positive value and to fill in \arg info_str, \arg
+ * chunks and \arg seconds appropriately.
+*/
+int (*get_file_info)(FILE *audio_file, char *info_str,
+       long unsigned *chunks, int *seconds);
+/**
+ *
+ *
+ * cleanup function of this audio format handler
+ *
+ * This close function should deallocate any resources
+ * associated with the current audio file. In particular, it is responsible
+ * for closing the file handle. It is assumed to succeed.
+*/
+void (*close_audio_file)(void);
+/**
+ *
+ *
+ * jump to another position in the current audio file
+ *
+ * This is called if a client issued the ff or jmp command with \a request
+ * being the number of the next chunk that should be sent out. Must return a
+ * positive value on success and a negative value on errors.
+*/
+int (*reposition_stream)(long unsigned request);
+/**
+ *
+ *
+ * function responsible for reading one data chunk.
+ *
+ * \a read_chunk() must return a pointer to the next chunk of data that should
+ * be sent out, or \p NULL on errors or if the end of the file was encountered.
+ *
+ * If it returns non-NULL, \a len must contain the length of the returned
+ * buffer (which may be zero if nothing has to be sent for some reason).
+ * Otherwise, \a len is used to distinguish between the eof and the error case:
+ * It must be zero in the eof case, or negative if an error occcured.
+*/
+char * (*read_chunk)(long unsigned chunk_num, ssize_t *len);
+};
+
+void afs_init(void);
+void afs_send_chunk(void);
+struct timeval *afs_preselect(void);
+const char *audio_format_name(int);
+unsigned int afs_playing(void);
+unsigned int afs_next(void);
+unsigned int afs_repos(void);
+unsigned int afs_paused(void);
+
diff --git a/audioc.c b/audioc.c
new file mode 100644 (file)
index 0000000..23f67fb
--- /dev/null
+++ b/audioc.c
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2005-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 audioc.c the client program used to connect to para_audiod */
+
+#include "audioc.cmdline.h"
+#include "para.h"
+#include "net.h"
+#include "string.h"
+
+struct gengetopt_args_info conf;
+char *tmpfifo;
+enum {E_SYNTAX, E_READ, E_WRITE, E_SOCKET, E_INIT_SOCK_ADDR, E_CONNECT, E_CREDENTIALS, E_SELECT, E_OVERRUN};
+
+
+void para_log(__unused int ll, __unused char* fmt,...) /* no logging */
+{
+}
+
+/* audioc does not use encryption */
+void (*crypt_function_recv)(unsigned long len, const unsigned char *indata, unsigned char *outdata) = NULL;
+void (*crypt_function_send)(unsigned long len, const unsigned char *indata, unsigned char *outdata) = NULL;
+
+static char *concat_args(const int argc, char * const *argv)
+{
+       int i; char *buf = NULL;
+       for (i = 0; i < argc; i++) {
+               buf = para_strcat(buf, argv[i]);
+               if (i != argc - 1)
+                       buf = para_strcat(buf, "\n");
+       }
+       return buf;
+}
+
+static char *configfile_exists(void)
+{
+       static char *config_file;
+        struct stat statbuf;
+
+
+       if (!config_file) {
+               char *home = para_homedir();
+               config_file = make_message("%s/.paraslash/audioc.conf", home);
+               free(home);
+       }
+        if (!stat(config_file, &statbuf))
+               return config_file;
+       return NULL;
+}
+
+int main(int argc, char *argv[])
+{
+       struct sockaddr_un unix_addr;
+       int ret = -E_SYNTAX, fd, loaded = 0;
+       char *cf, *socket_name, *randname = para_tmpname(), *tmpsocket_name = NULL,
+               *buf = NULL, *hn = para_hostname(), *args, *home = para_homedir();
+
+
+       if (cmdline_parser(argc, argv, &conf))
+               goto out;
+       cf = configfile_exists();
+       if (cf) {
+               if (cmdline_parser_configfile(cf, &conf, 0, 0, 0)) {
+                       fprintf(stderr, "parse error in config file\n");
+                       exit(EXIT_FAILURE);
+               }
+       }
+       args = conf.inputs_num?
+               concat_args(conf.inputs_num, conf.inputs) :
+               para_strdup("stat");
+       buf = para_malloc(conf.bufsize_arg);
+       if (conf.socket_given)
+               socket_name = para_strdup(conf.socket_arg);
+       else
+               socket_name = make_message(
+                       "/var/paraslash/audiod_socket.%s", hn);
+       if (conf.tmpdir_given)
+               tmpsocket_name = make_message("%s/audioc.sock.%s.%s",
+                       conf.tmpdir_arg, hn, randname);
+       else
+               tmpsocket_name = make_message("%s/.paraslash/audioc_sock.%s.%s",
+                       home, hn, randname);
+
+       ret = -E_SOCKET;
+       fd = create_pf_socket(tmpsocket_name, &unix_addr, S_IRUSR | S_IWUSR);
+       unlink(tmpsocket_name);
+       if (fd < 0)
+               goto out;
+       ret = -E_INIT_SOCK_ADDR;
+       if (init_unix_addr(&unix_addr, socket_name) < 0)
+               goto out;
+       ret = - E_CONNECT;
+       if (connect(fd, (struct sockaddr *)&unix_addr, UNIX_PATH_MAX) < 0)
+               goto out;
+       ret = -E_CREDENTIALS;
+       if (send_cred_buffer(fd, args) < 0)
+               goto out;
+       for (;;) {
+               int max_fileno = -1, check_write = 0;
+               ssize_t len;
+               fd_set rfd, wfd;
+               FD_ZERO(&rfd);
+               FD_ZERO(&wfd);
+               if (loaded && loaded > 10000)
+                       fprintf(stderr, "loaded: %d\n", loaded);
+               if (loaded < conf.bufsize_arg) {
+                       FD_SET(fd, &rfd);
+                       max_fileno = MAX(max_fileno, fd);
+               }
+               if (loaded > 0) {
+                       FD_SET(STDOUT_FILENO, &wfd);
+                       max_fileno = MAX(max_fileno, STDOUT_FILENO);
+                       check_write = 1;
+               }
+               ret = -E_OVERRUN;
+               if (max_fileno < 0)
+                       goto out;
+               ret = select(max_fileno + 1, &rfd, &wfd, NULL, NULL);
+               if (ret < 0) {
+                       ret = -E_SELECT;
+                       goto out;
+               }
+               if (loaded < conf.bufsize_arg && FD_ISSET(fd, &rfd)) {
+                       len = recv_bin_buffer(fd, buf + loaded, 
+                               conf.bufsize_arg - loaded);
+                       if (len <= 0) {
+                               ret = len < 0? -E_READ : 0;
+                               goto out;
+                       }
+                       loaded += len;
+               }
+               if (check_write && FD_ISSET(STDOUT_FILENO, &wfd)) {
+                       ret = write(STDOUT_FILENO, buf, loaded);
+                       if (ret < 0) {
+                               ret = -E_WRITE;
+                               goto out;
+                       }
+                       loaded -= ret;
+               }
+       }
+out:
+       if (!ret && loaded && buf)
+               ret = write(STDOUT_FILENO, buf, loaded);
+       return ret < 0? -ret : EXIT_SUCCESS;
+}
diff --git a/audioc.ggo b/audioc.ggo
new file mode 100644 (file)
index 0000000..8813338
--- /dev/null
@@ -0,0 +1,6 @@
+section "general options"
+option "loglevel" l "set loglevel (0-6)" int typestr="level" default="4" no
+option "socket" s "well-known socket (default=/var/paraslash/audiod.socket.$HOSTNAME)" string typestr="filename" no
+option "tmpdir" d "directory for temporary socket (default=~/.paraslash)" string typestr="dirname" no
+option "timeout" t "maximum time in seconds to wait for server response before giving up" int typestr="seconds" default="1" no
+option "bufsize" b "size of internal buffer" int typestr="bytes" default="8192" no
diff --git a/audiod.c b/audiod.c
new file mode 100644 (file)
index 0000000..552577d
--- /dev/null
+++ b/audiod.c
@@ -0,0 +1,1617 @@
+/*
+ * Copyright (C) 2005-2006 Andre Noll <noll@mathematik.tu-darmstadt.de>
+ *
+ *     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 audiod.c the paraslash's audio daemon */
+
+#include <sys/time.h> /* gettimeofday */
+#include "para.h"
+
+#include "audiod.cmdline.h"
+#include "list.h"
+#include "close_on_fork.h"
+#include "recv.h"
+#include "filter.h"
+#include "grab_client.cmdline.h"
+#include "grab_client.h"
+#include "ringbuffer.h"
+
+#include "error.h"
+#include "audiod.h"
+#include "net.h"
+#include "daemon.h"
+#include "string.h"
+
+/** define the array of error lists needed by para_audiod */
+INIT_AUDIOD_ERRLISTS;
+/** define the array containing all supported audio formats */
+DEFINE_AUDIO_FORMAT_ARRAY;
+
+/**
+ * the possible modes of operation
+ *
+ * - off: disconnect from para_server
+ * - on: receive status information from para_server and play the audio stream
+ * - sb: only receive status information but not the audio stream
+*/
+enum {AUDIOD_OFF, AUDIOD_ON, AUDIOD_STANDBY};
+
+/** defines how to handle one supported audio format */
+struct audio_format_info {
+/** pointer to the receiver for this audio format */
+       struct receiver *receiver;
+/** the receiver configuration */
+       void *receiver_conf;
+/** the number of filters that should be activated for this audio format */
+       unsigned int num_filters;
+/** pointer to the array of filters to be activated */
+       struct filter **filters;
+/** pointer to the array of filter configurations */
+       void **filter_conf;
+/** output of the last filter is written to stdin of this command */
+       char *write_cmd;
+/** do not start receiver/filters/writer before this time */
+       struct timeval restart_barrier;
+};
+
+/**
+ * describes one instance of a receiver-filter-writer chain
+ *
+ * \sa receier_node, receiver, filter, filter_node, filter_chain_info
+  */
+struct slot_info {
+/** number of the audio format in this slot */
+       int format;
+/** the file descriptor of the writer */
+       int write_fd;
+/** the process id of the writer */
+       pid_t wpid;
+/** time of the last successful read from the receiver */
+       struct timeval rtime;
+/** time the last write to the write fd happend */
+       struct timeval wtime;
+/** writer start time */
+       struct timeval wstime;
+/** did we include \a write_fd in the fdset */
+       int  wcheck;
+/** set to one if we have sent the TERM signal to \a wpid */
+       int wkilled;
+/** the receiver info associated with this slot */
+       struct receiver_node *receiver_node;
+/** the active filter chain */
+       struct filter_chain_info *fci;
+};
+
+static struct slot_info slot[MAX_STREAM_SLOTS];
+
+/** defines one command of para_audiod */
+struct audiod_command {
+/** the name of the command */
+const char *name;
+/** pointer to the function that handles the command */
+int (*handler)(int, int, char**);
+/** one-line description of the command */
+const char *description;
+/** summary of the command line options */
+const char *synopsis;
+/** the long help text */
+const char *help;
+};
+
+extern const char *status_item_list[NUM_STAT_ITEMS];
+
+static int com_grab(int, int, char **);
+static int com_cycle(int, int, char **);
+static int com_help(int, int, char **);
+static int com_off(int, int, char **);
+static int com_on(int, int, char **);
+static int com_sb(int, int, char **);
+static int com_stat(int, int, char **);
+static int com_term(int, int, char **);
+static int stat_pipe = -1, signal_pipe;
+
+static struct gengetopt_args_info conf;
+static struct timeval server_stream_start, sa_time_diff;
+static int playing, current_decoder = -1,
+       audiod_status = AUDIOD_ON, offset_seconds, length_seconds,
+       sa_time_diff_sign = 1, audiod_socket = -1;
+static char *af_status, /* the audio format announced in server status */
+       *socket_name, *hostname;
+/** how many status items to remember */
+#define RINGBUFFER_SIZE 32
+static void *stat_item_ringbuf;
+static FILE *logfile;
+static const struct timeval restart_delay = {0, 300 * 1000};
+
+static struct audio_format_info afi[] = {
+
+[AUDIO_FORMAT_MP3] =
+       {
+               .write_cmd = "para_play",
+       },
+[AUDIO_FORMAT_OGG] =
+       {
+               .write_cmd = "para_play",
+       },
+};
+
+static struct audiod_command cmds[] = {
+{
+.name = "cycle",
+.handler = com_cycle,
+.description = "switch to next mode",
+.synopsis = "cycle",
+.help =
+
+"on -> standby -> off -> on\n"
+
+},
+{
+.name = "grab",
+.handler = com_grab,
+.description = "grab the audio stream",
+.synopsis = "-- grab [grab_options]",
+.help =
+"grab ('splice') the audio stream at any position in the filter      \n"
+"chain and send that data back to the client. \n"
+"Available options:\n\n"
+GRAB_HELP_TXT
+},
+{
+.name = "help",
+.handler = com_help,
+.description = "display command list or help for given command",
+.synopsis = "help [command]",
+.help =
+
+"When I was younger, so much younger than today, I never needed\n"
+"anybody's help in any way. But now these days are gone, I'm not so\n"
+"self assured. Now I find I've changed my mind and opened up the doors.\n"
+"\n"
+"                              -- Beatles: Help\n"
+
+},
+{
+.name = "off",
+.handler = com_off,
+.description = "deactivate para_audiod",
+.synopsis = "off",
+.help =
+
+"Close connection to para_server and stop all decoders.\n"
+
+},
+{
+.name = "on",
+.handler = com_on,
+.description = "activate para_audiod",
+.synopsis = "on",
+.help =
+
+"Establish connection to para_server, retrieve para_server's current\n"
+"status. If playing, start corresponding decoder. Otherwise stop\n"
+"all decoders.\n"
+
+},
+{
+.name = "sb",
+.handler = com_sb,
+.description = "enter standby mode",
+.synopsis = "sb",
+.help =
+
+"Stop all decoders but leave connection to para_server open.\n"
+
+},
+{
+.name = "stat",
+.handler = com_stat,
+.description = "print status information",
+.synopsis = "stat",
+.help =
+
+"Add para_audiod status information to para_server's status information\n"
+"and dump everything to stdout.\n"
+
+},
+{
+.name = "term",
+.handler = com_term,
+.description = "terminate audiod",
+.synopsis = "term",
+.help =
+
+"Stop all decoders, shut down connection to para_server and exit.\n"
+
+},
+{
+.name = NULL,
+}
+};
+
+/** iterate over all slots */
+#define FOR_EACH_SLOT(slot) for (slot = 0; slot < MAX_STREAM_SLOTS; slot++)
+/** iterate over all supported audio formats */
+#define FOR_EACH_AUDIO_FORMAT(af) for (af = 0; af < NUM_AUDIO_FORMATS; af++)
+/** iterate over the array of all audiod commands */
+#define FOR_EACH_COMMAND(c) for (c = 0; cmds[c].name; c++)
+
+/**
+ * get the audio format number
+ * \param name the name of the audio format
+ *
+ * \return The audio format number on success, -E_UNSUPPORTED_AUDIO_FORMAT if
+ * \a name is not a supported audio format.
+ */
+int get_audio_format_num(char *name)
+{
+       int i;
+       FOR_EACH_AUDIO_FORMAT(i)
+               if (!strcmp(name, audio_formats[i]))
+                       return i;
+       return -E_UNSUPPORTED_AUDIO_FORMAT;
+}
+
+/*
+ * log function. first argument is loglevel.
+ */
+void para_log(int ll, char* fmt,...)
+{
+       va_list argp;
+       FILE *outfd;
+       struct tm *tm;
+       time_t t1;
+       char str[MAXLINE] = "";
+
+       if (ll < conf.loglevel_arg)
+               return;
+       if (!logfile && conf.logfile_given)
+               logfile = open_log(conf.logfile_arg);
+       if (!logfile && conf.daemon_given)
+               return;
+       if (!logfile) {
+               if (ll < WARNING)
+                       outfd = stdout;
+               else
+                       outfd = stderr;
+       } else
+               outfd = logfile;
+       time(&t1);
+       tm = localtime(&t1);
+       strftime(str, MAXLINE, "%b %d %H:%M:%S", tm);
+       fprintf(outfd, "%s %s ", str, hostname);
+       if (conf.loglevel_arg <= INFO)
+               fprintf(outfd, "%i ", ll);
+       va_start(argp, fmt);
+       vfprintf(outfd, fmt, argp);
+       va_end(argp);
+}
+
+static int client_write(int fd, const char *buf)
+{
+       size_t len = strlen(buf);
+       return write(fd, buf, len) != len? -E_CLIENT_WRITE: 1;
+}
+
+static char *get_time_string(struct timeval *newest_stime)
+{
+       struct timeval now, diff, adj_stream_start, tmp;
+       int total = 0, use_server_time = 1;
+
+       if (!playing)
+               return make_message("%s:", length_seconds?
+                       "" : status_item_list[SI_PLAY_TIME]);
+       if (audiod_status == AUDIOD_OFF)
+               goto out;
+       if (sa_time_diff_sign > 0)
+               tv_diff(&server_stream_start, &sa_time_diff,
+                       &adj_stream_start);
+       else
+               tv_add(&server_stream_start, &sa_time_diff,
+                       &adj_stream_start);
+       tmp = adj_stream_start;
+       if (newest_stime && audiod_status == AUDIOD_ON) {
+               tv_diff(newest_stime, &adj_stream_start, &diff);
+               if (tv2ms(&diff) < 5000) {
+                       tmp = *newest_stime;
+                       use_server_time = 0;
+               }
+       }
+       gettimeofday(&now, NULL);
+       tv_diff(&now, &tmp, &diff);
+       total = diff.tv_sec + offset_seconds;
+       if (total > length_seconds)
+               total = length_seconds;
+       if (total < 0)
+               total = 0;
+out:
+       return make_message(
+               "%s:%s%d:%02d [%d:%02d] (%d%%/%d:%02d)",
+               status_item_list[SI_PLAY_TIME],
+               use_server_time? "~" : "",
+               total / 60,
+               total % 60,
+               (length_seconds - total) / 60,
+               (length_seconds - total) % 60,
+               length_seconds? (total * 100 + length_seconds / 2) /
+                       length_seconds : 0,
+               length_seconds / 60,
+               length_seconds % 60
+       );
+}
+
+static char *audiod_status_string(void)
+{
+       int i;
+       struct timeval *newest_stime = NULL;
+       char *ret, *time_string, *uptime_string, *decoder_flags =
+               para_malloc((MAX_STREAM_SLOTS + 1) * sizeof(char));
+
+       FOR_EACH_SLOT(i) {
+               struct slot_info *s = &slot[i];
+               char flag = '0';
+               if (s->receiver_node)
+                       flag += 1;
+               if (s->wpid > 0)
+                       flag += 2;
+               if (flag != '0')
+                       flag += s->format * 4;
+               decoder_flags[i] = flag;
+               if (s->wpid <= 0)
+                       continue;
+               if (newest_stime && tv_diff(&s->wstime, newest_stime, NULL) <= 0)
+                       continue;
+               newest_stime = &s->wstime;
+       }
+       decoder_flags[MAX_STREAM_SLOTS] = '\0';
+       time_string = get_time_string(newest_stime);
+       uptime_string = uptime_str();
+       ret = make_message("%s:%s\n%s:%s\n%s:%s\n%s",
+               status_item_list[SI_AUDIOD_UPTIME], uptime_string,
+               status_item_list[SI_DECODER_FLAGS], decoder_flags,
+               status_item_list[SI_AUDIOD_STATUS], audiod_status == AUDIOD_ON?
+                       "on" : (audiod_status == AUDIOD_OFF? "off": "sb"),
+               time_string);
+       free(uptime_string);
+       free(decoder_flags);
+       free(time_string);
+       return ret;
+}
+
+static char *configfile_exists(void)
+{
+       static char *config_file;
+
+       if (!config_file) {
+               char *home = para_homedir();
+               config_file = make_message("%s/.paraslash/audiod.conf", home);
+               free(home);
+       }
+       return file_exists(config_file)? config_file : NULL;
+}
+
+static void setup_signal_handling(void)
+{
+       signal_pipe = para_signal_init();
+       PARA_INFO_LOG("signal pipe: fd %d\n", signal_pipe);
+       para_install_sighandler(SIGINT);
+       para_install_sighandler(SIGTERM);
+       para_install_sighandler(SIGCHLD);
+       para_install_sighandler(SIGHUP);
+       signal(SIGPIPE, SIG_IGN);
+}
+
+static void audiod_status_dump(void)
+{
+       static char *prev_status;
+       char *tmp = audiod_status_string();
+
+       if (!prev_status || strcmp(tmp, prev_status))
+               stat_client_write(tmp);
+       free(prev_status);
+       prev_status = tmp;
+}
+
+static void clear_slot(int slot_num)
+{
+       struct slot_info *s = &slot[slot_num];
+
+       PARA_INFO_LOG("clearing slot %d\n", slot_num);
+       memset(s, 0, sizeof(struct slot_info));
+       s->format = -1;
+}
+
+static void kill_stream_writer(int slot_num)
+{
+       struct slot_info *s = &slot[slot_num];
+
+       if (s->format < 0 || s->wkilled || s->wpid <= 0)
+               return;
+       PARA_DEBUG_LOG("kill -TERM %d (%s stream writer in slot %d)\n",
+               s->wpid, audio_formats[s->format], slot_num);
+       kill(s->wpid, SIGTERM);
+       s->wkilled = 1;
+       s->fci->error = 1;
+}
+
+static void close_receiver(int slot_num)
+{
+       struct slot_info *s = &slot[slot_num];
+       struct audio_format_info *a;
+       struct timeval now;
+
+       if (s->format < 0 || !s->receiver_node)
+               return;
+       a = &afi[s->format];
+       PARA_NOTICE_LOG("closing %s recevier in slot %d\n",
+               audio_formats[s->format] , slot_num);
+       a->receiver->close(s->receiver_node);
+       free(s->receiver_node);
+       s->receiver_node = NULL;
+       gettimeofday(&now, NULL);
+       tv_add(&now, &restart_delay, &a->restart_barrier); /* FIXME: Use set_restart_barrier() */
+}
+
+static void kill_all_decoders(void)
+{
+       int i;
+
+       FOR_EACH_SLOT(i)
+               if (slot[i].format >= 0) {
+                       PARA_INFO_LOG("stopping decoder in slot %d\n", i);
+                       kill_stream_writer(i);
+               }
+}
+
+static void set_restart_barrier(int format, struct timeval *now)
+{
+       struct timeval tmp;
+
+       if (now)
+               tmp = *now;
+       else
+               gettimeofday(&tmp, NULL);
+       tv_add(&tmp, &restart_delay, &afi[format].restart_barrier);
+}
+
+static void check_sigchld(void)
+{
+       pid_t pid;
+       int i;
+       struct timeval now;
+       gettimeofday(&now, NULL);
+
+reap_next_child:
+       pid = para_reap_child();
+       if (pid <= 0)
+               return;
+       FOR_EACH_SLOT(i) {
+               struct slot_info *s = &slot[i];
+               long lifetime;
+               if (s->format < 0)
+                       continue;
+               if (pid == s->wpid) {
+                       s->wpid = -1;
+                       lifetime = now.tv_sec - s->wstime.tv_sec;
+                       PARA_INFO_LOG("%s stream writer in slot %d died "
+                               "after %li secs\n",
+                               audio_formats[s->format], i, lifetime);
+                       set_restart_barrier(s->format, &now);
+                       goto reap_next_child;
+               }
+       }
+       PARA_CRIT_LOG("para_client died (pid %d)\n", pid);
+       goto reap_next_child;
+}
+
+static int get_empty_slot(void)
+{
+       int i;
+       struct slot_info *s;
+
+       FOR_EACH_SLOT(i) {
+               s = &slot[i];
+               if (s->format < 0) {
+                       clear_slot(i);
+                       return i;
+               }
+               if (s->write_fd > 0 || s->wpid > 0)
+                       continue;
+               if (s->receiver_node)
+                       continue;
+               if (s->fci)
+                       continue;
+               clear_slot(i);
+               return i;
+       }
+       return -E_NO_MORE_SLOTS;
+}
+
+static int decoder_running(int format)
+{
+       int i, ret = 0;
+       struct slot_info *s;
+
+       FOR_EACH_SLOT(i) {
+               s = &slot[i];
+               if (s->format == format && s->receiver_node)
+                       ret |= 1;
+               if (s->format == format && s->wpid > 0)
+                       ret |= 2;
+       }
+       return ret;
+}
+
+static void close_stat_pipe(void)
+{
+       char *msg;
+       int i;
+
+       if (stat_pipe < 0)
+               return;
+       PARA_NOTICE_LOG("%s", "closing status pipe\n");
+       close(stat_pipe);
+       del_close_on_fork_list(stat_pipe);
+       stat_pipe = -1;
+       kill_all_decoders();
+       for (i = 0; i < RINGBUFFER_SIZE; i++)
+               ringbuffer_add(stat_item_ringbuf, para_strdup(NULL));
+       dump_empty_status();
+       length_seconds = 0;
+       offset_seconds = 0;
+       audiod_status_dump();
+       playing = 0;
+       msg = make_message("%s:no connection to para_server\n",
+               status_item_list[SI_STATUS_BAR]);
+       ringbuffer_add(stat_item_ringbuf, msg);
+       stat_client_write(msg);
+       free(msg);
+}
+
+static void __noreturn clean_exit(int status, const char *msg)
+{
+       PARA_EMERG_LOG("%s\n", msg);
+       kill_all_decoders();
+       if (socket_name)
+               unlink(socket_name);
+       if (stat_pipe >= 0)
+               close_stat_pipe();
+       exit(status);
+}
+
+static char *glob_cmd(char *cmd)
+{
+       char *ret, *replacement;
+       struct timeval tmp, delay, rss; /* real stream start */
+
+       delay.tv_sec = conf.stream_delay_arg / 1000;
+       delay.tv_usec = (conf.stream_delay_arg % 1000) * 1000;
+//     PARA_INFO_LOG("delay: %lu:%lu\n", delay.tv_sec, delay.tv_usec);
+       if (sa_time_diff_sign < 0)
+               tv_add(&server_stream_start, &sa_time_diff, &rss);
+       else
+               tv_diff(&server_stream_start, &sa_time_diff, &rss);
+       tv_add(&rss, &delay, &tmp);
+       replacement = make_message("%lu:%lu", tmp.tv_sec, tmp.tv_usec);
+       ret = s_a_r(cmd, "STREAM_START", replacement);
+       free(replacement);
+       if (!ret)
+               goto out;
+       PARA_INFO_LOG("cmd: %s, repl: %s\n", cmd, ret);
+       {
+       struct timeval now;
+       gettimeofday(&now, NULL);
+       PARA_INFO_LOG("now: %lu:%lu\n", now.tv_sec, now.tv_usec);
+       }
+out:
+       return ret;
+}
+
+/** get the number of filters for the given audio format */
+int num_filters(int audio_format_num)
+{
+       return afi[audio_format_num].num_filters;
+}
+
+static void open_filters(int slot_num)
+{
+       struct slot_info *s = &slot[slot_num];
+       struct audio_format_info *a = &afi[s->format];
+       int nf = a->num_filters;
+       int i;
+
+       s->fci = para_calloc(sizeof(struct filter_chain_info));
+       INIT_LIST_HEAD(&s->fci->filters);
+       if (!nf)
+               return;
+       s->fci->inbuf = s->receiver_node->buf;
+       s->fci->in_loaded = &s->receiver_node->loaded;
+       s->fci->outbuf = s->receiver_node->buf;
+       s->fci->out_loaded = &s->receiver_node->loaded;
+       s->fci->eof = &s->receiver_node->eof;
+       for (i = 0; i < nf; i++) {
+               struct filter_node *fn = para_calloc(sizeof(struct filter_node));
+               fn->conf = a->filter_conf[i];
+               fn->fci = s->fci;
+               fn->filter = a->filters[i];
+               INIT_LIST_HEAD(&fn->callbacks);
+               list_add_tail(&fn->node, &s->fci->filters);
+               fn->filter->open(fn);
+               PARA_NOTICE_LOG("%s filter %d/%d (%s) started in slot %d\n",
+                       audio_formats[s->format], i + 1,  nf,
+                       fn->filter->name, slot_num);
+               s->fci->outbuf = fn->buf;
+               s->fci->out_loaded = &fn->loaded;
+       }
+       PARA_DEBUG_LOG("output buffer for filter chain %p: %p\n", s->fci,
+               s->fci->outbuf);
+}
+
+static struct filter_node *find_filter_node(int slot_num, int format, int filternum)
+{
+       struct filter_node *fn;
+       int i, j;
+
+       FOR_EACH_SLOT(i) {
+               struct slot_info *s = &slot[i];
+               if (s->format < 0 || !s->fci)
+                       continue;
+               if (slot_num >= 0 && slot_num != i)
+                       continue;
+               if (format >= 0 && s->format != format)
+                       continue;
+               if (num_filters(i) < filternum)
+                       continue;
+               /* success */
+               j = 1;
+               list_for_each_entry(fn, &s->fci->filters, node)
+                       if (filternum <= 0 || j++ == filternum)
+                               break;
+               return fn;
+       }
+       return NULL;
+}
+
+static void start_stream_writer(int slot_num)
+{
+       int ret, fds[3] = {1, -1, -1};
+       struct slot_info *s = &slot[slot_num];
+       struct audio_format_info *a = &afi[s->format];
+       char *glob = glob_cmd(a->write_cmd);
+
+       PARA_INFO_LOG("starting stream writer: %s\n", glob? glob : a->write_cmd);
+       open_filters(slot_num);
+
+       ret = para_exec_cmdline_pid(&s->wpid, glob? glob : a->write_cmd, fds);
+       free(glob);
+       if (ret < 0) {
+               PARA_ERROR_LOG("exec failed (%d)\n", ret);
+               return;
+       }
+       s->write_fd = fds[0];
+       add_close_on_fork_list(s->write_fd);
+       /* we write to this fd in do_select, so we need non-blocking */
+       fcntl(s->write_fd, F_SETFL, O_NONBLOCK);
+       gettimeofday(&s->wstime, NULL);
+       current_decoder = slot_num;
+       activate_inactive_grab_clients(slot_num, s->format, &s->fci->filters);
+}
+
+static void open_receiver(int format)
+{
+       struct audio_format_info *a = &afi[format];
+       struct slot_info *s;
+       int ret, slot_num;
+
+       slot_num = get_empty_slot();
+       if (slot_num < 0)
+               clean_exit(EXIT_FAILURE, PARA_STRERROR(-slot_num));
+       s = &slot[slot_num];
+       s->format = format;
+       gettimeofday(&s->rtime, NULL);
+       s->wtime = s->rtime;
+       s->receiver_node = para_calloc(sizeof(struct receiver_node));
+       s->receiver_node->conf = a->receiver_conf;
+       ret = a->receiver->open(s->receiver_node);
+       if (ret < 0) {
+               PARA_ERROR_LOG("failed to open receiver (%s)\n",
+                       PARA_STRERROR(-ret));
+               free(s->receiver_node);
+               s->receiver_node = NULL;
+               return;
+       }
+       PARA_NOTICE_LOG("started %s: %s receiver in slot %d\n",
+               audio_formats[s->format], a->receiver->name, slot_num);
+}
+
+static int is_frozen(int format)
+{
+       struct timeval now;
+       struct audio_format_info *a = &afi[format];
+
+       gettimeofday(&now, NULL);
+       return (tv_diff(&now, &a->restart_barrier, NULL) > 0)? 0 : 1;
+}
+
+static void start_current_receiver(void)
+{
+       int i;
+
+       if (!af_status)
+               return;
+       i = get_audio_format_num(af_status);
+       if (i < 0)
+               return;
+       if ((decoder_running(i) & 1) || is_frozen(i))
+               return;
+       open_receiver(i);
+}
+
+static void compute_time_diff(const struct timeval *status_time)
+{
+       struct timeval now, tmp, diff;
+       static int count;
+       int sign;
+       const struct timeval max_deviation = {0, 500 * 1000};
+       const int time_smooth = 5;
+
+       gettimeofday(&now, NULL);
+       sign = tv_diff(status_time, &now, &diff);
+//             PARA_NOTICE_LOG("%s: sign = %i, sa_time_diff_sign = %i\n", __func__,
+//                     sign, sa_time_diff_sign);
+       if (!count) {
+               sa_time_diff_sign = sign;
+               sa_time_diff = diff;
+               count++;
+               return;
+       }
+       if (count > 5) {
+               int s = tv_diff(&diff, &sa_time_diff, &tmp);
+               if (tv_diff(&max_deviation, &tmp, NULL) < 0)
+                       PARA_WARNING_LOG("time diff jump: %lims\n",
+                               s * tv2ms(&tmp));
+       }
+       count++;
+       sa_time_diff_sign = tv_convex_combination(
+               sa_time_diff_sign * time_smooth, &sa_time_diff,
+               count > 10? sign : sign * time_smooth, &diff,
+               &tmp);
+       sa_time_diff = tmp;
+       PARA_INFO_LOG("time diff (cur/avg): "
+               "%li:%lu/%li:%lu\n",
+               sign * diff.tv_sec, (diff.tv_usec + 500) / 1000,
+               sa_time_diff_sign * sa_time_diff.tv_sec,
+               (sa_time_diff.tv_usec + 500)/ 1000);
+}
+
+static void check_stat_line(char *line)
+{
+       int itemnum;
+       size_t ilen = 0;
+       struct timeval tv;
+
+       if (!line)
+               return;
+       ringbuffer_add(stat_item_ringbuf, line);
+       stat_client_write(line);
+       itemnum = stat_line_valid(line);
+       if (itemnum < 0)
+               return;
+       ilen = strlen(status_item_list[itemnum]);
+       switch (itemnum) {
+       case SI_STATUS:
+               playing = strstr(line, "playing")? 1 : 0;
+               break;
+       case SI_FORMAT:
+               free(af_status);
+               af_status = para_strdup(line + ilen + 1);
+               break;
+       case SI_OFFSET:
+               offset_seconds = atoi(line + ilen + 1);
+               break;
+       case SI_LENGTH:
+               length_seconds = atoi(line + ilen + 1);
+               break;
+       case SI_STREAM_START:
+               if (sscanf(line + ilen + 1, "%lu.%lu",
+                               &tv.tv_sec, &tv.tv_usec) == 2)
+                       server_stream_start = tv;
+               break;
+       case SI_CURRENT_TIME:
+               if (sscanf(line + ilen + 1, "%lu.%lu", &tv.tv_sec,
+                               &tv.tv_usec) == 2)
+                       compute_time_diff(&tv);
+               break;
+       }
+}
+
+static void handle_signal(int sig)
+{
+       switch (sig) {
+       case SIGCHLD:
+               return check_sigchld();
+       case SIGINT:
+       case SIGTERM:
+       case SIGHUP:
+               PARA_EMERG_LOG("terminating on signal %d\n", sig);
+               clean_exit(EXIT_FAILURE, "caught deadly signal");
+               return;
+       }
+}
+
+static void check_timeouts(void)
+{
+       struct timeval now;
+       int slot_num, timeout = conf.stream_timeout_arg;
+
+       gettimeofday(&now, NULL);
+       FOR_EACH_SLOT(slot_num) {
+               struct slot_info *s = &slot[slot_num];
+               if (s->format < 0)
+                       continue;
+               /* check read time */
+               if (s->receiver_node &&
+                       now.tv_sec > s->rtime.tv_sec + timeout) {
+                       PARA_INFO_LOG("%s input buffer (slot %d) not ready\n",
+                               audio_formats[s->format], slot_num);
+                       if (s->fci)
+                               s->fci->error = 42;
+                       else
+                               close_receiver(slot_num);
+               }
+               /* check write time */
+               if (s->wpid > 0 && !s->wkilled &&
+                       now.tv_sec > s->wtime.tv_sec + timeout) {
+                       PARA_INFO_LOG("%s output buffer (slot %d) not ready\n",
+                               audio_formats[s->format], slot_num);
+                       if (s->fci)
+                               s->fci->error = 42;
+               }
+       }
+}
+
+static size_t get_loaded_bytes(int slot_num)
+{
+       size_t loaded = 0;
+       struct slot_info *s = &slot[slot_num];
+       struct receiver_node *rn = s->receiver_node;
+
+       if (s->format < 0)
+               goto out;
+
+       if (afi[s->format].num_filters) {
+               if (s->fci)
+                       loaded = *s->fci->out_loaded;
+       } else {
+               if (rn)
+                       loaded = rn->loaded;
+       }
+out:
+       return loaded;
+}
+
+
+static void close_decoder_if_idle(int slot_num)
+{
+       struct slot_info *s = &slot[slot_num];
+       struct receiver_node *rn = s->receiver_node;
+
+       if (s->format < 0)
+               return;
+       if (!s->fci)
+               return;
+       if (!rn->eof && !s->fci->error && s->wpid > 0)
+               return;
+       if (!s->fci->error && s->wpid > 0) { /* eof */
+               if (filter_io(s->fci) > 0)
+                       return;
+               if (get_loaded_bytes(slot_num))
+                       return;
+       }
+       if (s->write_fd > 0) {
+               PARA_INFO_LOG("slot %d: closing write fd %d\n", slot_num,
+                       s->write_fd);
+               close(s->write_fd);
+               del_close_on_fork_list(s->write_fd);
+               s->write_fd = -1;
+       }
+       if (s->wpid > 0)
+               return; /* wait until writer dies before closing filters */
+       PARA_INFO_LOG("closing all filters in slot %d (filter_chain %p)\n",
+               slot_num, s->fci);
+       close_filters(s->fci);
+       free(s->fci);
+       close_receiver(slot_num);
+       clear_slot(slot_num);
+}
+
+static int set_stream_fds(fd_set *wfds)
+{
+       int i, max_fileno = -1;
+
+       check_timeouts();
+       FOR_EACH_SLOT(i) {
+               struct slot_info *s = &slot[i];
+               struct audio_format_info *a;
+               struct receiver_node *rn;
+
+               close_decoder_if_idle(i);
+               s->wcheck = 0;
+               if (s->format < 0)
+                       continue;
+               a = &afi[s->format];
+               rn = s->receiver_node;
+               if (rn && rn->loaded && !s->wpid) {
+                       PARA_INFO_LOG("no writer in slot %d\n", i);
+                       start_stream_writer(i);
+               }
+               if (s->write_fd <= 0)
+                       continue;
+               if (!get_loaded_bytes(i))
+                       continue;
+               FD_SET(s->write_fd, wfds);
+               s->wcheck = 1;
+               max_fileno = MAX(s->write_fd, max_fileno);
+       }
+//     PARA_INFO_LOG("return %d\n", max_fileno);
+       return max_fileno;
+}
+
+static int write_audio_data(int slot_num)
+{
+       struct slot_info *s = &slot[slot_num];
+       struct audio_format_info *a = &afi[s->format];
+       struct receiver_node *rn = s->receiver_node;
+       int rv;
+       char **buf;
+       size_t *len;
+
+       if (a->num_filters) {
+               buf = &s->fci->outbuf;
+               len = s->fci->out_loaded;
+       } else {
+               buf = &rn->buf;
+               len = &rn->loaded;
+       }
+       PARA_DEBUG_LOG("writing %p (%d bytes)\n", *buf, *len);
+       rv = write(s->write_fd, *buf, *len);
+       PARA_DEBUG_LOG("wrote %d/%d\n", rv, *len);
+       if (rv < 0) {
+               PARA_WARNING_LOG("write error in slot %d (fd %d): %s\n",
+                       slot_num, s->write_fd, strerror(errno));
+               *len = 0;
+               s->fci->error = E_WRITE_AUDIO_DATA;
+       } else if (rv != *len) {
+               PARA_DEBUG_LOG("partial %s write (%i/%i) for slot %d\n",
+                       audio_formats[s->format], rv, *len, slot_num);
+               *len -= rv;
+               memmove(*buf, *buf + rv, *len);
+       } else
+               *len = 0;
+       if (rv > 0)
+               gettimeofday(&s->wtime, NULL);
+       return rv;
+}
+
+static void slot_io(fd_set *wfds)
+{
+       int ret, i;
+
+       FOR_EACH_SLOT(i) {
+               struct slot_info *s = &slot[i];
+               struct receiver_node *rn = s->receiver_node;
+
+               if (rn && rn->loaded)
+                       gettimeofday(&s->rtime, NULL);
+               if (s->format >= 0 && s->write_fd > 0 && s->fci) {
+                       ret = filter_io(s->fci);
+                       if (ret < 0)
+                               s->fci->error = -ret;
+//                     PARA_DEBUG_LOG("slot %d, filter io %d bytes, check write: %d, loaded: %d/%d, eof: %d\n",
+//                              i, ret, s->wcheck, rn->loaded, *s->fci->out_loaded, rn->eof);
+               }
+               if (s->write_fd <= 0 || !s->wcheck || !FD_ISSET(s->write_fd, wfds))
+                       continue;
+               write_audio_data(i);
+       }
+}
+
+static int parse_stream_command(const char *txt, char **cmd)
+{
+       char *p = strchr(txt, ':');
+       int i;
+
+       if (!p)
+               return -E_MISSING_COLON;
+       p++;
+       FOR_EACH_AUDIO_FORMAT(i) {
+               if (strncmp(txt, audio_formats[i], strlen(audio_formats[i])))
+                       continue;
+               *cmd = p;
+               return i;
+       }
+       return -E_UNSUPPORTED_AUDIO_FORMAT;
+}
+
+static int add_filter(int format, char *cmdline)
+{
+       struct audio_format_info *a = &afi[format];
+       int filter_num, nf = a->num_filters;
+
+       filter_num = check_filter_arg(cmdline, &a->filter_conf[nf]);
+       if (filter_num < 0)
+               return filter_num;
+       a->filters[nf] = &filters[filter_num];
+       a->num_filters++;
+       PARA_INFO_LOG("%s filter %d: %s\n", audio_formats[format], nf + 1,
+               a->filters[nf]->name);
+       return filter_num;
+}
+
+static int setup_default_filters(void)
+{
+       int i, ret = 1;
+
+       FOR_EACH_AUDIO_FORMAT(i) {
+               struct audio_format_info *a = &afi[i];
+               char *tmp;
+               int j;
+               if (a->num_filters)
+                       continue;
+               /* add "dec" to audio format name */
+               tmp = make_message("%sdec", audio_formats[i]);
+               for (j = 0; filters[j].name; j++)
+                       if (!strcmp(tmp, filters[j].name))
+                               break;
+               free(tmp);
+               ret = -E_UNSUPPORTED_FILTER;
+               if (!filters[j].name)
+                       goto out;
+               tmp = para_strdup(filters[j].name);
+               ret = add_filter(i, tmp);
+               free(tmp);
+               if (ret < 0)
+                       goto out;
+               PARA_INFO_LOG("%s -> default filter: %s\n", audio_formats[i], filters[j].name);
+               ret = add_filter(i, "wav");
+               if (ret < 0)
+                       goto out;
+               PARA_INFO_LOG("%s -> default filter: wav\n", audio_formats[i]);
+       }
+out:
+       return ret;
+}
+
+static int init_stream_io(void)
+{
+       int i, ret, receiver_num;
+       char *cmd;
+
+       for (i = 0; i < conf.stream_write_cmd_given; i++) {
+               ret = parse_stream_command(conf.stream_write_cmd_arg[i], &cmd);
+               if (ret < 0)
+                       goto out;
+               afi[ret].write_cmd = para_strdup(cmd);
+               PARA_INFO_LOG("%s write command: %s\n", audio_formats[ret], afi[ret].write_cmd);
+       }
+       for (i = 0; receivers[i].name; i++) {
+               PARA_INFO_LOG("initializing %s receiver\n", receivers[i].name);
+               receivers[i].init(&receivers[i]);
+       }
+       for (i = 0; i < conf.receiver_given; i++) {
+               char *arg = conf.receiver_arg[i];
+               char *recv = strchr(arg, ':');
+               ret = -E_MISSING_COLON;
+               if (!recv)
+                       goto out;
+               *recv = '\0';
+               recv++;
+               ret = get_audio_format_num(arg);
+               if (ret < 0)
+                       goto out;
+               afi[ret].receiver_conf = check_receiver_arg(recv, &receiver_num);
+               if (!afi[ret].receiver_conf) {
+                       ret = -E_RECV_SYNTAX;
+                       goto out;
+               }
+               afi[ret].receiver = &receivers[receiver_num];
+       }
+       /* use the first available receiver with no arguments
+        * for those audio formats for which no receiver
+        * was specified
+        */
+       cmd = para_strdup(receivers[0].name);
+       FOR_EACH_AUDIO_FORMAT(i) {
+               struct audio_format_info *a = &afi[i];
+               if (a->receiver_conf)
+                       continue;
+               a->receiver_conf = check_receiver_arg(cmd, &receiver_num);
+               if (!a->receiver_conf)
+                       return -E_RECV_SYNTAX;
+               a->receiver = &receivers[receiver_num];
+       }
+       free(cmd);
+       /* filters */
+       filter_init(filters);
+       FOR_EACH_AUDIO_FORMAT(i) {
+               afi[i].filter_conf = para_malloc((conf.filter_given + 1) * sizeof(char *));
+               afi[i].filters = para_malloc((conf.filter_given + 1) * sizeof(struct filter *));
+       }
+       if (!conf.no_default_filters_given)
+               return setup_default_filters();
+       for (i = 0; i < conf.filter_given; i++) {
+               char *arg = conf.filter_arg[i];
+               char *filter_name = strchr(arg, ':');
+               ret = -E_MISSING_COLON;
+               if (!filter_name)
+                       goto out;
+               *filter_name = '\0';
+               filter_name++;
+               ret = get_audio_format_num(arg);
+               if (ret < 0)
+                       goto out;
+               ret = add_filter(ret, filter_name);
+               if (ret < 0)
+                       goto out;
+       }
+       ret = 1;
+out:
+       return ret;
+}
+
+static int dump_commands(int fd)
+{
+       char *buf = para_strdup(""), *tmp = NULL;
+       int i;
+       ssize_t ret;
+
+       FOR_EACH_COMMAND(i) {
+               tmp = make_message("%s%s\t%s\n", buf, cmds[i].name,
+                       cmds[i].description);
+               free(buf);
+               buf = tmp;
+       }
+       ret = client_write(fd, buf);
+       free(buf);
+       return ret;
+}
+
+/*
+ * command handlers don't close their fd on errors (ret < 0) so that
+ * its caller can send an error message. Otherwise (ret >= 0) it's up
+ * to each individual command to close the fd if necessary.  
+ */
+
+static int com_help(int fd, int argc, char **argv)
+{
+       int i, ret;
+       char *buf;
+       const char *dflt = "No such command. Available commands:\n";
+
+       if (argc < 2) {
+               ret = dump_commands(fd);
+               goto out;
+       }
+       FOR_EACH_COMMAND(i) {
+               if (strcmp(cmds[i].name, argv[1]))
+                       continue;
+               buf = make_message(
+                       "NAME\n\t%s -- %s\n"
+                       "SYNOPSIS\n\tpara_audioc %s\n"
+                       "DESCRIPTION\n%s\n",
+                       argv[1],
+                       cmds[i].description,
+                       cmds[i].synopsis,
+                       cmds[i].help
+               );
+               ret = client_write(fd, buf);
+               free(buf);
+               goto out;
+       }
+       ret = client_write(fd, dflt);
+       if (ret > 0)
+               ret = dump_commands(fd);
+out:
+       if (ret >= 0)
+               close(fd);
+       return ret;
+}
+
+static int com_stat(int fd, __unused int argc, __unused char **argv)
+{
+       int i, ret;
+       char *buf = audiod_status_string();
+
+       buf = para_strcat(buf, "\n");
+       for (i = RINGBUFFER_SIZE - 1; i >= 0; i--) {
+               char *tmp, *line = ringbuffer_get(stat_item_ringbuf, i);
+               if (!line)
+                       continue;
+               tmp = make_message("%s\n", line);
+               buf = para_strcat(buf, tmp);
+               free(tmp);
+       }
+       ret = client_write(fd, buf);
+       if (ret > 0)
+               ret = stat_client_add(fd);
+       free(buf);
+       return ret;
+}
+
+#if 0
+static char *list_filters(void)
+{
+       int i, j;
+       char *tmp, *msg = make_message("format\tnum\tcmd\n");
+
+       FOR_EACH_AUDIO_FORMAT(i) {
+               for (j = 0; j < afi[i].num_filters; j++) {
+                       tmp = make_message("%s\t%i\t%s\n",
+                               afi[i].name, j, afi[i].filter_cmds[j]);
+                       msg = para_strcat(msg, tmp);
+                       free(tmp);
+               }
+               tmp = make_message("%s\t%i\t%s\n", afi[i].name,
+                       j, afi[i].write_cmd);
+               msg = para_strcat(msg, tmp);
+               free(tmp);
+       }
+       return msg;
+}
+#endif
+
+static int com_grab(int fd, int argc, char **argv)
+{
+       struct grab_client *gc;
+       struct filter_node *fn;
+       int err;
+
+       PARA_INFO_LOG("argc: %d, argv[0]: %s, optind: %d\n", argc, argv[0], optind);
+       gc = grab_client_new(fd, argc, argv, &err);
+       PARA_INFO_LOG("argc: %d, argv[0]: %s, optind: %d\n", argc, argv[0], optind);
+       if (!gc)
+               goto err_out;
+       fn = find_filter_node(gc->conf->slot_arg, gc->audio_format_num, gc->conf->filter_num_arg);
+       if (fn)
+               activate_grab_client(gc, fn);
+       return 1;
+err_out:
+       if (err != -E_GC_HELP_GIVEN)
+               return err;
+       err = client_write(fd, "Usage: para_audioc [audioc_options] -- "
+               "grab [grab_options]\nAvailable options:\n");
+       if (err < 0)
+               return err;
+       err = client_write(fd, GRAB_HELP_TXT);
+       if (err < 0)
+               return err;
+       close(fd);
+       return 1;
+}
+
+static int __noreturn com_term(int fd, __unused int argc, __unused char **argv)
+{
+       close(fd);
+       clean_exit(EXIT_SUCCESS, "terminating on user request");
+}
+
+static int com_on(int fd, __unused int argc, __unused char **argv)
+{
+       audiod_status = AUDIOD_ON;
+       close(fd);
+       return 1;
+}
+
+static int com_off(int fd, __unused int argc, __unused char **argv)
+{
+       audiod_status = AUDIOD_OFF;
+       close(fd);
+       return 1;
+}
+
+static int com_sb(int fd, __unused int argc, __unused char **argv)
+{
+       audiod_status = AUDIOD_STANDBY;
+       close(fd);
+       return 1;
+}
+
+static int com_cycle(int fd, int argc, char **argv)
+{
+       switch (audiod_status) {
+               case  AUDIOD_ON:
+                       return com_sb(fd, argc, argv);
+                       break;
+               case  AUDIOD_OFF:
+                       return com_on(fd, argc, argv);
+                       break;
+               case  AUDIOD_STANDBY:
+                       return com_off(fd, argc, argv);
+                       break;
+       }
+       close(fd);
+       return 1;
+}
+
+static int check_perms(struct ucred *c)
+{
+       int i;
+
+       if (!conf.user_allow_given)
+               return 1;
+       for (i = 0; i < conf.user_allow_given; i++)
+               if (c->uid == conf.user_allow_arg[i])
+                       return 1;
+       return -E_UCRED_PERM;
+}
+
+static int handle_connect(void)
+{
+       int i, argc, ret, clifd = -1;
+       struct ucred c;
+       char *buf = para_malloc(MAXLINE), **argv = NULL;
+       struct sockaddr_un unix_addr;
+
+       ret = para_accept(audiod_socket, &unix_addr, sizeof(struct sockaddr_un));
+       if (ret < 0)
+               goto out;
+       clifd = ret;
+       ret = recv_cred_buffer(clifd, buf, MAXLINE - 1, &c);
+       if (ret < 0)
+               goto out;
+       PARA_INFO_LOG("pid: %i, uid: %i, gid: %i, ret: %i, buf: %s\n", c.pid, c.uid, c.gid, ret, buf);
+       buf[ret] = '\0';
+       ret = check_perms(&c);
+       if (ret < 0)
+               goto out;
+       argc = split_args(buf, &argv, '\n');
+       PARA_INFO_LOG("argv[0]: %s\n", argv[0]);
+       for (i = 0; cmds[i].name; i++) {
+               if (strcmp(cmds[i].name, argv[0]))
+                       continue;
+               ret = cmds[i].handler(clifd, argc + 1, argv);
+               goto out;
+       }
+       ret = -E_INVALID_AUDIOD_CMD; /* cmd not found */
+out:
+       free(buf);
+       free(argv);
+       if (clifd > 0 && ret < 0 && ret != -E_CLIENT_WRITE) {
+               char *tmp = make_message("%s\n", PARA_STRERROR(-ret));
+               client_write(clifd, tmp);
+               free(tmp);
+               close(clifd);
+       }
+       return ret;
+}
+
+static void audiod_get_socket(void)
+{
+       struct sockaddr_un unix_addr;
+
+       if (conf.socket_given)
+               socket_name = para_strdup(conf.socket_arg);
+       else {
+               char *hn = para_hostname();
+               socket_name = make_message("/var/paraslash/audiod_socket.%s",
+                       hn);
+               free(hn);
+       }
+       PARA_NOTICE_LOG("connecting to local socket %s\n", socket_name);
+       if (conf.force_given)
+               unlink(socket_name);
+       audiod_socket = create_pf_socket(socket_name, &unix_addr,
+                       S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IWOTH);
+       if (audiod_socket < 0) {
+               PARA_EMERG_LOG("%s", "can not connect to socket\n");
+               exit(EXIT_FAILURE); /* do not unlink socket */
+       }
+       if (listen(audiod_socket, 5) < 0) {
+               PARA_EMERG_LOG("%s", "can not listen on socket\n");
+               exit(EXIT_FAILURE); /* do not unlink socket */
+       }
+       add_close_on_fork_list(audiod_socket);
+}
+
+static int open_stat_pipe(void)
+{
+       int ret, fd[3] = {-1, 1, 0};
+       char *argv[] = {BINDIR "/para_client", "stat", NULL};
+       pid_t pid;
+       ret = para_exec(&pid, BINDIR "/para_client",  argv, fd);
+       if (ret >= 0) {
+               ret = fd[1];
+               PARA_NOTICE_LOG("stat pipe opened, fd %d\n", ret);
+               add_close_on_fork_list(ret);
+       } else
+               clean_exit(EXIT_FAILURE, "failed to open status pipe");
+       return ret;
+}
+
+static int pre_select(fd_set *rfds, fd_set *wfds, struct timeval *tv)
+{
+       int i, ret, max = -1;
+
+       FOR_EACH_SLOT(i) {
+               struct slot_info *s = &slot[i];
+               struct audio_format_info *a;
+               struct receiver_node *rn = s->receiver_node;
+               if (s->format < 0 || !rn)
+                       continue;
+               a = &afi[s->format];
+               ret = a->receiver->pre_select(rn, rfds, wfds, tv);
+//             PARA_NOTICE_LOG("%s preselect: %d\n", a->receiver->name, ret);
+               max = MAX(max, ret);
+       }
+       return max;
+
+}
+static void audiod_post_select(int select_ret, fd_set *rfds, fd_set *wfds)
+{
+       int i, ret;
+
+       FOR_EACH_SLOT(i) {
+               struct slot_info *s = &slot[i];
+               struct audio_format_info *a;
+               struct receiver_node *rn = s->receiver_node;
+               if (s->format < 0 || !rn || rn->eof)
+                       continue;
+               a = &afi[s->format];
+               ret = a->receiver->post_select(rn, select_ret, rfds, wfds);
+               if (ret <= 0) {
+                       if (ret)
+                               PARA_ERROR_LOG("%s post select failed: %s (slot %d)\n",
+                               a->receiver->name, PARA_STRERROR(-ret), i);
+                       else
+                               PARA_INFO_LOG("eof in slot %d\n", i);
+                       rn->eof = 1;
+               }
+               if (ret < 0 && s->fci)
+                       s->fci->error = ret;
+       }
+}
+
+/* TODO: move everything before the select call to pre_select() */
+static void __noreturn audiod_mainloop(void)
+{
+       fd_set rfds, wfds;
+       int ret, max_fileno, sbo = 0;
+       char status_buf[STRINGSIZE] = "";
+       struct timeval tv;
+repeat:
+       FD_ZERO(&rfds);
+       FD_ZERO(&wfds);
+       max_fileno = 0;
+       if (audiod_status != AUDIOD_ON)
+               kill_all_decoders();
+       else if (playing)
+               start_current_receiver();
+       max_fileno = set_stream_fds(&wfds);
+       /* stat pipe (read) */
+       if (stat_pipe >= 0 && audiod_status == AUDIOD_OFF)
+               close_stat_pipe();
+       if (stat_pipe < 0 && audiod_status != AUDIOD_OFF) {
+               stat_pipe = open_stat_pipe();
+               sbo = 0;
+               status_buf[0] = '\0';
+       }
+       if (stat_pipe >= 0 && audiod_status != AUDIOD_OFF) {
+               FD_SET(stat_pipe, &rfds);
+               max_fileno = MAX(max_fileno, stat_pipe);
+       }
+       /* always check signal pipe */
+       FD_SET(signal_pipe, &rfds);
+       max_fileno = MAX(max_fileno, signal_pipe);
+       /* local socket */
+       if (audiod_socket < 0)
+               audiod_get_socket(); /* doesn't return on errors */
+       FD_SET(audiod_socket, &rfds);
+       max_fileno = MAX(max_fileno, audiod_socket);
+       tv.tv_sec = 0;
+       tv.tv_usec = 200 * 1000;
+       ret = pre_select(&rfds, &wfds, &tv);
+       max_fileno = MAX(max_fileno, ret);
+       ret = select(max_fileno + 1, &rfds, &wfds, NULL, &tv);
+       if (ret < 0 && errno != EINTR)
+               PARA_ERROR_LOG("select returned %d (%s)\n", ret,
+                       strerror(errno));
+       if (audiod_status != AUDIOD_OFF)
+               audiod_status_dump();
+       if (ret < 0)
+               goto repeat;
+       audiod_post_select(ret, &rfds, &wfds);
+       /* read status pipe */
+       if (stat_pipe >=0 && FD_ISSET(stat_pipe, &rfds)) {
+               ret = read(stat_pipe, status_buf + sbo, STRINGSIZE - 1 - sbo);
+               if (ret <= 0) {
+                       close_stat_pipe();
+                       /* avoid busy loop if server is down */
+                       while (sleep(1) > 0)
+                               ; /* try again*/
+               } else {
+                       status_buf[ret + sbo] = '\0';
+                       sbo = for_each_line(status_buf, ret + sbo,
+                               &check_stat_line, 0);
+               }
+       }
+       slot_io(&wfds);
+       if (FD_ISSET(audiod_socket, &rfds)) {
+               ret = handle_connect();
+               if (ret < 0) {
+                       PARA_ERROR_LOG("%s\n", PARA_STRERROR(-ret));
+               }
+       }
+       /* signals */
+       if (FD_ISSET(signal_pipe, &rfds)) {
+               int sig_nr = para_next_signal();
+               if (sig_nr > 0)
+                       handle_signal(sig_nr);
+       }
+       goto repeat;
+}
+
+static void set_initial_status(void)
+{
+       audiod_status = AUDIOD_ON;
+       if (!conf.mode_given)
+               return;
+       if (!strcmp(conf.mode_arg, "sb")) {
+               audiod_status = AUDIOD_STANDBY;
+               return;
+       }
+       if (!strcmp(conf.mode_arg, "off")) {
+               audiod_status = AUDIOD_OFF;
+               return;
+       }
+       if (strcmp(conf.mode_arg, "on"))
+               PARA_WARNING_LOG("%s", "invalid mode\n");
+}
+
+int __noreturn main(int argc, char *argv[])
+{
+       char *cf;
+       int i;
+
+       valid_fd_012();
+       hostname = para_hostname();
+       cmdline_parser(argc, argv, &conf);
+       para_drop_privileges(conf.user_arg);
+       cf = configfile_exists();
+       if (cf) {
+               if (cmdline_parser_configfile(cf, &conf, 0, 0, 0)) {
+                       fprintf(stderr, "parse error in config file\n");
+                       exit(EXIT_FAILURE);
+               }
+       }
+       log_welcome("para_audiod", conf.loglevel_arg);
+       i = init_stream_io();
+       if (i < 0) {
+               fprintf(stderr, "init stream io error: %s\n",
+                       PARA_STRERROR(-i));
+               exit(EXIT_FAILURE);
+       }
+       server_uptime(UPTIME_SET);
+       set_initial_status();
+       FOR_EACH_SLOT(i)
+               clear_slot(i);
+       stat_item_ringbuf = ringbuffer_new(RINGBUFFER_SIZE);
+       init_grabbing();
+       setup_signal_handling();
+       if (conf.daemon_given)
+               daemon_init();
+       audiod_mainloop();
+}
diff --git a/audiod.ggo b/audiod.ggo
new file mode 100644 (file)
index 0000000..cafc851
--- /dev/null
@@ -0,0 +1,100 @@
+section "general options"
+option "user" u "run as user 'name'. Read the output of 'para_server -h' for a detailed information on this option." string typestr="name" no
+option "loglevel" l "set loglevel (0-6)" int typestr="level" default="4" no
+option "daemon" d "run as background daemon" flag off
+option "force" F "force startup even if socket exits" flag off
+option "logfile" L "(default=stdout/stderr)" string typestr="filename" no
+option "mode" m "mode to use on startup (on/off/sb)" string typestr="mode" default="on" no
+option "socket" s "well-known socket to listen on (default=/var/paraslash/audiod_sock.<host_name>)" string typestr="filename" no
+
+option "user_allow" -
+
+"allow this user to connect to para_audiod.
+May be specified multiple times. If not
+specified at all, allow all users to
+connect."
+
+int typestr="uid" default="-1" no multiple
+
+section "stream i/o options."
+
+#################
+
+option "receiver" r "Select receiver.
+
+May be given multiple times, once for each
+supported audio format. receiver_spec
+consists of an audio format, the receiver
+name and any options for that receiver,
+seperated by colons.
+
+Example:
+
+-r mp3:http:-i:www.paraslash.org:-p:8009
+"
+
+string typestr="receiver_spec" default="http" no multiple
+
+#################
+option "no_default_filters" D "Configure filters manually.
+
+If (and only if) this option is set, the
+--filter options take effect.  Otherwise, the
+compiled-in default filters mp3dec (oggdec)
+and wav are activated for mp3 (ogg) streams."
+
+flag off
+#################
+
+option "filter" f "Select filter(s) manually.
+
+May be given multiple times. filter_spec
+consists of an audio format, the name of the
+filter, and any options for that filter,
+separated by colons.
+
+Examples:
+       -f mp3:mp3dec
+       -f:mp3:compress:--anticlip:--volume:2
+
+Note that these options are ignored by default,
+see --no_default_filters."
+
+string typestr="filter_spec" no multiple
+
+#################
+
+option "stream_write_cmd" w
+
+"Specify stream writer.
+
+May be given multiple times, once for each
+supported audio format. Default value is
+'para_play' for both mp3 and ogg. You can use
+the START_TIME() macro for these commands.
+Each occurence of START_TIME() gets replaced
+at runtime by the stream start time announced
+by para_server, plus any offsets."
+
+string typestr="format:command" no multiple
+
+#################
+
+option "stream_delay" -
+
+"Time to add to para_server's start_time.
+
+Amount of time to be added to the server
+stream start time for stream_write_cmd if
+START_TIME() was given. Useful for
+syncronizing the audio output of clients."
+
+int typestr="milliseconds" default="200" no
+
+#################
+
+option "stream_timeout" -
+
+"Deactivate slot if idle for that many seconds"
+
+int typestr="seconds" default="30" no
diff --git a/audiod.h b/audiod.h
new file mode 100644 (file)
index 0000000..e6bd9b3
--- /dev/null
+++ b/audiod.h
@@ -0,0 +1,12 @@
+/** \file audiod.h symbols exported from audiod.c */
+int num_filters(int audio_format_num);
+int get_audio_format_num(char *name);
+enum {
+       AUDIO_FORMAT_MP3,
+       AUDIO_FORMAT_OGG
+};
+#define NUM_AUDIO_FORMATS (AUDIO_FORMAT_OGG + 1)
+extern const char *audio_formats[];
+#define DEFINE_AUDIO_FORMAT_ARRAY const char *audio_formats[] = {"mp3", "ogg", NULL}
+#define MAX_STREAM_SLOTS 5
+
diff --git a/autogen.sh b/autogen.sh
new file mode 100755 (executable)
index 0000000..5c950ba
--- /dev/null
@@ -0,0 +1,12 @@
+#!/bin/sh
+echo preparing...
+if test -f Makefile; then
+       make maintainer-clean > /dev/null
+fi
+aclocal &> /dev/null
+autoconf
+autoheader 
+echo configuring...
+./configure $@ > /dev/null
+echo compiling...
+make clean all > /dev/null
diff --git a/bash_completion b/bash_completion
new file mode 100644 (file)
index 0000000..2f07499
--- /dev/null
@@ -0,0 +1,38 @@
+_para()
+{
+local cur prev sect i manpath tmp
+COMPREPLY=()
+cur=${COMP_WORDS[COMP_CWORD]}
+prev=${COMP_WORDS[COMP_CWORD-1]}
+
+#_expand || return 0
+
+case "${COMP_WORDS[1]}" in
+       cs|cs|strdel|strq)
+       COMPREPLY=( $( eval  para_client streams | grep "^$cur" \
+               2>/dev/null ) )
+       return 0
+       ;;
+       info|la|us|ca|ls|pic|us)
+       COMPREPLY=( $( eval  para_client ls "$cur%"  ) )
+       return 0
+       ;;
+       sa)
+       COMPREPLY=( $( eval  para_client laa | grep "^$cur" \
+               2>/dev/null ) )
+       return 0
+       
+
+       
+esac
+# default completion if parameter contains /
+# [[ "$cur" == */* ]] && return 0
+#echo "cur=$cur"
+COMPREPLY=( $( eval  para_client help | cut -f 1 | sed 1d | grep ^$cur 2>/dev/null ) )
+# weed out directory path names and paths to man pages
+COMPREPLY=( ${COMPREPLY[@]##*/?(:)} )
+COMPREPLY=( ${COMPREPLY[@]} $( compgen -G $cur\*.[0-9ln] ) )
+return 0
+} 
+complete -F _para -o default para
+complete -F _para -o default para_client
diff --git a/client.c b/client.c
new file mode 100644 (file)
index 0000000..1ece195
--- /dev/null
+++ b/client.c
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 1997-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 client.c the client program used to connect to para_server */
+
+#include "para.h"
+#include "config.h"
+#include <readline/readline.h>
+#include <readline/history.h>
+#include "client.cmdline.h"
+#include "crypt.h"
+#include "rc4.h"
+#include <openssl/rc4.h>
+#include "net.h"
+#include "string.h"
+
+/* A static variable for holding the line. */
+static char *line_read;
+
+struct gengetopt_args_info args_info;
+
+/*
+ * client log function
+ */
+void para_log(int ll, char* fmt,...)
+{
+       va_list argp;
+       FILE *outfd;
+
+       /* ignore log message if loglevel is not high enough */
+       if (ll < args_info.loglevel_arg)
+               return;
+       if (ll < WARNING)
+               outfd = stdout;
+       else
+               outfd = stderr;
+       va_start(argp, fmt);
+       vfprintf(stdout, fmt, argp);
+       va_end(argp);
+}
+
+/*
+ * Read a string, and return a pointer to it. Returns NULL on EOF.
+ */
+static char *rl_gets(void)
+{
+       free(line_read);
+       /* Get a line from the user. */
+       line_read = readline("para_client> ");
+       /* If the line has any text in it, save it on the history. */
+       if (line_read && *line_read)
+               add_history(line_read);
+       return line_read;
+}
+
+/*
+ * do several cleanups on sigint
+ */
+static void sigint_handler(__unused int i)
+{
+       rl_cleanup_after_signal();
+       rl_reset_after_signal();
+}
+
+void get_options(int argc, char *argv[],
+       char **config_file, char **key_file)
+{
+       char *home;
+       static char default_key_file[_POSIX_PATH_MAX] = "";
+       static char default_config_file[_POSIX_PATH_MAX] = "";
+       struct stat statbuf;
+       int ret;
+
+       cmdline_parser(argc, argv, &args_info);
+       if (!args_info.user_given)
+               args_info.user_arg = para_logname();
+       if (!args_info.key_file_given) {
+               home = para_homedir();
+               sprintf(default_key_file, "%s/.paraslash/key.%s", home,
+                       args_info.user_arg);
+               free(home);
+       }
+       if (!args_info.config_file_given) {
+               home = para_homedir();
+               sprintf(default_config_file, "%s/.paraslash/client.conf",
+                       home);
+               free(home);
+       }
+       if (!args_info.config_file_given)
+               *config_file = default_config_file;
+       else
+               *config_file = args_info.config_file_arg;
+       ret = stat(*config_file, &statbuf);
+       if (ret && args_info.config_file_given) {
+               fprintf(stderr, "can not stat config file %s\n",
+                       args_info.config_file_arg);
+               exit(EXIT_FAILURE);
+       }
+       if (!ret)
+               cmdline_parser_configfile(*config_file, &args_info, 0, 0, 0);
+       if (!args_info.key_file_given)
+               *key_file = default_key_file;
+       else
+               *key_file = args_info.key_file_arg;
+}
+
+static RC4_KEY rc4_recv_key;
+static RC4_KEY rc4_send_key;
+static unsigned char rc4_buf[2 * RC4_KEY_LEN];
+
+static void rc4_send(unsigned long len, const unsigned char *indata, unsigned char *outdata)
+{
+       RC4(&rc4_send_key, len, indata, outdata);
+}
+
+static void rc4_recv(unsigned long len, const unsigned char *indata, unsigned char *outdata)
+{
+       RC4(&rc4_recv_key, len, indata, outdata);
+}
+void (*crypt_function_recv)(unsigned long len, const unsigned char *indata, unsigned char *outdata);
+void (*crypt_function_send)(unsigned long len, const unsigned char *indata, unsigned char *outdata);
+
+
+static void append_str(char **data, const char* append)
+{
+       if (*data) {
+               char *tmp = make_message("%s\n%s", *data, append);
+               free(*data);
+               *data = tmp;
+       } else
+               *data = para_strdup(append);
+}
+
+/*
+ * MAIN
+ */
+int main(int argc, char *argv[])
+{
+
+       int sockfd, numbytes, i, interactive, received, ret;
+       struct hostent *he;
+       struct sockaddr_in their_addr;
+       char *command = NULL;
+       char buf[8192];
+       char *auth_str;
+       char *key_file, *config_file;
+       long unsigned challenge_nr;
+       char *line;
+
+       get_options(argc, argv, &config_file, &key_file);
+       if (args_info.loglevel_arg <= NOTICE)
+               cmdline_parser_print_version();
+       PARA_INFO_LOG(
+               "current loglevel: %d\n"
+               "using config_file: %s\n"
+               "using key_file: %s\n"
+               "connecting to %s:%d\n",
+               args_info.loglevel_arg,
+               config_file,
+               key_file,
+               args_info.hostname_arg,
+               args_info.server_port_arg
+       );
+       interactive = args_info.inputs_num == 0? 1 : 0;
+       if (interactive) {
+               PARA_NOTICE_LOG("%s", "no command, entering interactive mode\n");
+               signal(SIGINT, sigint_handler);
+       } else {
+               /* not interactive, concat args */
+               for (i = 0; i < args_info.inputs_num; i++)
+                       append_str(&command, args_info.inputs[i]);
+       }
+interactive_loop:
+       crypt_function_recv = NULL;
+       crypt_function_send = NULL;
+       if (interactive) {
+               int i = 0;
+               char *p;
+
+               rl_save_prompt();
+               rl_message("\n");
+               rl_kill_full_line(0, 0);
+               rl_free_line_state();
+               /* read a line via readline */
+               line = rl_gets();
+               if (!line)
+                       return 0;
+               if (!line[0])
+                       goto interactive_loop;
+               p = line;
+               while (sscanf(p, "%200s%n", buf, &i) == 1) {
+                       append_str(&command, buf);
+                       p += i;
+               }
+       }
+       /* get the host info */
+       PARA_NOTICE_LOG("getting host info of %s\n",
+               args_info.hostname_arg);
+       if (!(he = get_host_info(args_info.hostname_arg)))
+               exit(EXIT_FAILURE);
+       /* get new socket */
+       if ((sockfd = get_socket()) < 0)
+               exit(EXIT_FAILURE);
+       /* init their_addr */
+       init_sockaddr(&their_addr, args_info.server_port_arg, he);
+       /* Connect */
+       PARA_NOTICE_LOG("connecting to %s...\n",
+               args_info.hostname_arg);
+       if (para_connect(sockfd, &their_addr) < 0)
+               exit(EXIT_FAILURE);
+       /* Receive Welcome message */
+       if ((numbytes = recv_buffer(sockfd, buf, sizeof(buf))) < 0)
+               exit(EXIT_FAILURE);
+       /* send auth command */
+       auth_str = make_message("auth %s%s", args_info.plain_given?  "" : "rc4 ",
+               args_info.user_arg);
+       PARA_INFO_LOG("<-- %s--> %s\n", buf, auth_str);
+       if (send_buffer(sockfd, auth_str) < 0)
+               exit(EXIT_FAILURE);
+       /* receive challenge number */
+       if ((numbytes = recv_buffer(sockfd, buf, sizeof(buf))) < 0)
+               exit(EXIT_FAILURE);
+       if (numbytes != 64) {
+               PARA_EMERG_LOG("did not receive valid challenge (got %i bytes)\n",
+                       numbytes);
+               buf[numbytes] = '\0';
+               PARA_ERROR_LOG("received the following instead: %s\n", buf);
+               goto write_out;
+       }
+       PARA_INFO_LOG("<-- [challenge (%i bytes)]\n", numbytes);
+       /* decrypt challenge number */
+       ret = para_decrypt_challenge(key_file, &challenge_nr, (unsigned char *) buf,
+               numbytes);
+       if (ret < 0) {
+               PARA_EMERG_LOG("decrypt error (%d). Bad secret key?\n", ret);
+               exit(EXIT_FAILURE);
+       }
+       /* send decrypted challenge */
+       PARA_INFO_LOG("--> %lu\n", challenge_nr);
+       if (send_va_buffer(sockfd, "%s%lu", CHALLENGE_RESPONSE_MSG, challenge_nr) < 0)
+               exit(EXIT_FAILURE);
+       /* Wait for approval */
+       PARA_NOTICE_LOG("%s", "waiting for approval from server\n");
+       if ((numbytes = recv_buffer(sockfd, buf, sizeof(buf))) < 0)
+               exit(EXIT_FAILURE);
+       PARA_INFO_LOG("++++ server info ++++\n%s\n++++ end of server "
+               "info ++++\n", buf);
+       /* Check if server has sent "Proceed" message */
+       if (!strstr(buf, PROCEED_MSG)) {
+               PARA_EMERG_LOG("%s", "authentication failed\n");
+               exit(EXIT_FAILURE);
+       }
+       if (numbytes >= PROCEED_MSG_LEN + 32) {
+               PARA_INFO_LOG("%s", "decrypting session key\n");
+               if (para_decrypt_buffer(key_file, rc4_buf,
+                               (unsigned char *)buf + PROCEED_MSG_LEN + 1,
+                               numbytes - PROCEED_MSG_LEN - 1) < 0) {
+                       PARA_EMERG_LOG("%s", "error receiving rc4 key\n");
+                       exit(EXIT_FAILURE);
+               }
+               RC4_set_key(&rc4_send_key, RC4_KEY_LEN, rc4_buf);
+               RC4_set_key(&rc4_recv_key, RC4_KEY_LEN, rc4_buf + RC4_KEY_LEN);
+               PARA_INFO_LOG("rc4 encrytion activated: %x:%x:%x:%x\n",
+                       rc4_buf[0], rc4_buf[1], rc4_buf[2], rc4_buf[3]);
+               crypt_function_recv = rc4_recv;
+               crypt_function_send = rc4_send;
+       }
+       /* send command */
+       PARA_INFO_LOG("--> %s\n", command);
+       if (send_buffer(sockfd, command) < 0)
+               exit(EXIT_FAILURE);
+       free(command);
+       command = NULL;
+       if (send_buffer(sockfd, EOC_MSG "\n") < 0)
+               exit(EXIT_FAILURE);
+       PARA_NOTICE_LOG("%s", "command sent.\n");
+write_out:
+       received = 0;
+       /* write server output to stdout */
+       while ((numbytes = recv_bin_buffer(sockfd, buf, sizeof(buf))) > 0) {
+               int ret;
+
+               if (!received && strstr(buf, AWAITING_DATA_MSG)) {
+                       PARA_NOTICE_LOG("%s", "<-- awaiting data\n");
+                       PARA_NOTICE_LOG("%s", "--> sending stdin\n");
+                       while ((ret = read(STDIN_FILENO, buf,
+                                       sizeof(buf))) > 0)
+                               send_bin_buffer(sockfd, buf, ret);
+                       PARA_NOTICE_LOG("%s", "closing connection\n");
+                       numbytes = 1;
+                       break;
+               }
+               received = 1;
+               if (write(STDOUT_FILENO, buf, numbytes) != numbytes)
+                       break;
+       }
+       if (!numbytes)
+               PARA_NOTICE_LOG("%s", "connection closed by peer\n");
+       close(sockfd);
+       if (interactive)
+               goto interactive_loop;
+       return 0;
+}
diff --git a/client.ggo b/client.ggo
new file mode 100644 (file)
index 0000000..bc267d4
--- /dev/null
@@ -0,0 +1,8 @@
+# file client.conf
+option "hostname" i "ip or host to connect" string typestr="host" default="localhost" no
+option "user" u "paraslash username" string typestr="username" default="<current user>" no
+option "server_port" p "port to connect" int typestr="port" default="2990" no
+option "key_file" k "(default='~/.paraslash/key.<user>')" string typestr="filename" no
+option "loglevel" l "set loglevel (0-6)" int typestr="number" default="5" no
+option "config_file" c "(default='~/.paraslash/client.conf')" string typestr="filename" no
+option "plain" - "request an uncrypted session" flag off
diff --git a/close_on_fork.c b/close_on_fork.c
new file mode 100644 (file)
index 0000000..74b4194
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2005-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 close_on_fork.c manage a list of fds that should be closed on fork */
+#include "para.h"
+#include "list.h"
+#include "string.h"
+
+static struct list_head close_on_fork_list;
+static int initialized;
+
+/**
+ * describes an element of the close-on-fork list
+ *
+ * \sa list.h
+ */
+struct close_on_fork {
+       /** the file descriptor which should be closed after fork() */
+       int fd;
+       /** the position in the close-on-fork list */
+       struct list_head node;
+};
+
+/**
+ * add one file descriptor to the close-on-fork list
+ *
+ * \param fd the file descriptor to add
+ */
+void add_close_on_fork_list(int fd)
+{
+       struct close_on_fork *cof = para_malloc(sizeof(struct close_on_fork));
+
+       if (!initialized) {
+               INIT_LIST_HEAD(&close_on_fork_list);
+               initialized = 1;
+       }
+       cof->fd = fd;
+       list_add(&cof->node, &close_on_fork_list);
+}
+
+
+/**
+ * delete one file descriptor from the close-on-fork list
+ *
+ * \param fd the file descriptor to delete
+ *
+ * Noop if \a fd does not belong to the close-on-fork list.
+ */
+void del_close_on_fork_list(int fd)
+{
+       struct close_on_fork *cof, *tmp;
+
+       if (!initialized)
+               return;
+       list_for_each_entry_safe(cof, tmp, &close_on_fork_list, node) {
+               if (fd != cof->fd)
+                       continue;
+               list_del(&cof->node);
+               free(cof);
+       }
+}
+
+/**
+ * call close(3) for each fd in the close-on-fork list
+ */
+void close_listed_fds(void)
+{
+       struct close_on_fork *cof;
+
+       if (!initialized)
+               return;
+       list_for_each_entry(cof, &close_on_fork_list, node) {
+               PARA_DEBUG_LOG("closing fd %d\n", cof->fd);
+               close(cof->fd);
+       }
+}
diff --git a/close_on_fork.h b/close_on_fork.h
new file mode 100644 (file)
index 0000000..1bd0cd1
--- /dev/null
@@ -0,0 +1,4 @@
+/** \file close_on_fork.h exported symbols from close_on_fork.c */
+void del_close_on_fork_list(int fd);
+void add_close_on_fork_list(int fd);
+void close_listed_fds(void);
diff --git a/command.c b/command.c
new file mode 100644 (file)
index 0000000..81cb283
--- /dev/null
+++ b/command.c
@@ -0,0 +1,1239 @@
+/*
+ * Copyright (C) 1997-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 command.c does client authentication and executes server commands */
+
+
+#include <malloc.h> /* mallinfo */
+#include <sys/time.h> /* gettimeofday */
+#include "crypt.h"
+#include "server.cmdline.h"
+#include "db.h"
+#include "server.h"
+#include "afs.h"
+#include "send.h"
+#include "rc4.h"
+#include <openssl/rc4.h>
+#include "gcc-compat.h"
+#include "error.h"
+#include "net.h"
+#include "daemon.h"
+#include "string.h"
+
+void (*crypt_function_recv)(unsigned long len, const unsigned char *indata,
+       unsigned char *outdata) = NULL;
+void (*crypt_function_send)(unsigned long len, const unsigned char *indata,
+       unsigned char *outdata) = NULL;
+static RC4_KEY rc4_recv_key;
+static RC4_KEY rc4_send_key;
+static unsigned char rc4_buf[2 * RC4_KEY_LEN];
+
+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_format afl[];
+extern struct sender senders[];
+extern char *user_list;
+struct sockaddr_in *in_addr;
+
+static int com_si(int, int, char **);
+static int com_version(int, int, char **);
+static int com_sb(int, int, char **);
+static int com_sc(int, int, char **);
+static int com_stat(int, int, char **);
+static int com_help(int, int, char **);
+static int com_hup(int, int, char **);
+static int com_term(int, int, char **);
+static int com_play(int, int, char **);
+static int com_stop(int, int, char **);
+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_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,
+.perms = DB_READ | DB_WRITE,
+.description = "change database tool",
+.synopsis = "cdt [name_of_new_dbtool]",
+.help =
+"Deactivate current dbtool and activate name_of_new_dbtool. If no\n"
+"argument was given, print the current database tool.\n"
+},
+
+{
+.name = "ff",
+.handler = com_ff,
+.perms = AFS_READ | AFS_WRITE,
+.description = "jmp amount of time forwards or backwards "
+       "in current audio file",
+.synopsis = "ff n[-]",
+.help =
+
+"\tSet the 'R' (reposition request) bit of the afs status flags\n"
+"\tand enqueue a request to jump n seconds forwards or backwards\n"
+"\tin the current audio file.\n"
+"\n"
+"EXAMPLE\n"
+"\n"
+"\t\tff 30-\n"
+"\n"
+"\tjumps 30 seconds backwards.\n"
+
+},
+
+{
+.name = "help",
+.handler = com_help,
+.perms = 0,
+.description = "print help text",
+.synopsis = "help [command]",
+.help =
+
+"Without any arguments, help prints a list of availible commands. When\n"
+"issued with a command name as first argument, print out a description\n"
+"for that command.\n"
+
+},
+
+{
+.name = "hup",
+.handler = com_hup,
+.perms = AFS_WRITE,
+.description = "force reload of config file and log file",
+.synopsis = "hup",
+.help =
+
+"After rereading the config file, a signal is sent to all children\n"
+"which forces them to close/reopen the log file.\n"
+
+},
+
+{
+.name = "jmp",
+.handler = com_jmp,
+.perms = AFS_READ | AFS_WRITE,
+.description = "jmp to given position in current audio file",
+.synopsis = "jmp [n]",
+.help =
+
+"\tSet the 'R' (reposition request) bit of the afs status flags\n"
+"\tand enqueue a request to jump to n% of the current audio file,\n"
+"\twhere 0 <= n <= 100.\n"
+
+},
+
+{
+.name = "next",
+.handler = com_next,
+.perms = AFS_READ | AFS_WRITE,
+.description = "skip rest of current audio file",
+.synopsis = "next",
+.help =
+
+"\tSet the 'N' (next audio file) bit of the afs status flags. When\n"
+"\tplaying, change audio file immediately. Equivalent to stop\n"
+"\tif paused, NOP if stopped.\n"
+
+
+},
+
+{
+.name = "nomore",
+.handler = com_nomore,
+.perms = AFS_READ | AFS_WRITE,
+.description = "stop playing after current audio file",
+.synopsis = "nomore",
+.help =
+
+"Set the 'O' (no more) bit of the afs status flags. This instructs\n"
+"para_server to clear the 'P' (playing) bit as soon as it encounters\n"
+"the 'N' (next audio file) bit being set.\n"
+"\n"
+"Use this command instead of stop if you don't like\n"
+"sudden endings.\n"
+
+},
+
+{
+.name ="pause",
+.handler = com_pause,
+.perms = AFS_READ | AFS_WRITE,
+.description = "pause current audio file",
+.synopsis = "pause",
+.help =
+
+"\tClear the 'P' (playing) bit of the afs status flags.\n"
+
+},
+
+{
+.name = "play",
+.handler = com_play,
+.perms = AFS_READ | AFS_WRITE,
+.description = "start playing or resume playing when paused",
+.synopsis = "play",
+.help =
+
+"\tSet the 'P' (playing) bit of the afs status flags. This\n"
+"\tresults in starting/continuing to stream.\n"
+
+},
+
+{
+.name = "sb",
+.handler = com_sb,
+.perms = AFS_READ,
+.description = "print status bar for current audio file",
+.synopsis = "sb [n]",
+.help =
+
+"Without any arguments, sb continuously prints a status bar of the form\n"
+"\n"
+"      12:34 [56:12] (56%) filename\n"
+"\n"
+"indicating playing time, remaining time, percentage and the name of\n"
+"the file beeing streamed. Use the optional number n to let stat exit\n"
+"after having displayed the status bar n times.\n"
+
+},
+{
+.name = "sc",
+.handler = com_sc,
+.perms = AFS_READ,
+.description = "print name of audio file whenever it changes",
+.synopsis = "sc [n]",
+.help =
+
+"\tsc prints exactly one line (the filename of the audio file\n"
+"\tbeing played) whenever the audio file changes. Stops after\n"
+"\tn iterations, or never if n is not specified.\n"
+
+},
+{
+.name = "sender",
+.handler = com_sender,
+.perms = AFS_READ | AFS_WRITE,
+.description = "control paraslash internal senders",
+.synopsis = "sender [s cmd [arguments]]",
+.help =
+
+"send command cmd to sender s. cmd may be one of the following:\n"
+"help, on, off, add, delete, allow, or deny. Note that not all senders\n"
+"support each command. Try e.g. 'para_client sender http help' for\n"
+"more information about the http sender. If no argument is given,\n"
+"print out a list of all senders that are compiled in.\n"
+
+},
+{
+.name = "si",
+.handler = com_si,
+.perms = 0,
+.description = "print server info",
+.synopsis = "si",
+.help =
+"Print server uptime and other information.\n"
+},
+
+{
+.name = "stat",
+.handler = com_stat,
+.perms = AFS_READ,
+.description = "print status info for current audio file",
+.synopsis = "stat [n]",
+.help =
+
+"\tWithout any arguments, stat continuously prints status messages\n"
+"\tof the audio file being streamed. Use the optional number n\n"
+"\tto let stat exit after having displayed status n times.\n"
+
+},
+
+{
+.name = "stop",
+.handler = com_stop,
+.perms = AFS_READ | AFS_WRITE,
+.description = "stop playing",
+.synopsis = "stop",
+.help =
+
+"\tClear the 'P' (play) bit and set the 'N' bit of the afs status\n"
+"\tflags.\n"
+
+},
+{
+.name = "term",
+.handler = com_term,
+.perms = AFS_READ | AFS_WRITE,
+.description = "terminate para_server",
+.synopsis = "term",
+.help =
+
+"Shuts down the server. Instead of this command, you can also send\n"
+"SIGINT or SIGTERM. It should never be necessary to send SIGKILL.\n"
+
+},
+{
+.name = "version",
+.handler = com_version,
+.perms = 0,
+.description = "print server's version",
+.synopsis = "version",
+.help =
+"Show version and other info\n"
+},
+/* this indicates the end of the list. Do not touch. */
+{
+.name = NULL,
+}
+};
+
+static void dummy(__unused int s)
+{}
+
+static void mmd_dup(struct misc_meta_data *new_mmd)
+{
+       mmd_lock();
+       *new_mmd = *mmd;
+       mmd_unlock();
+}
+
+/*
+ * compute human readable string containing
+ * afs_status for given integer value
+ */
+static char *afs_status_tohuman(unsigned int flags)
+{
+       if (flags & AFS_PLAYING)
+               return para_strdup("playing");
+       else if (flags & AFS_NEXT)
+               return para_strdup("stopped");
+       else
+               return para_strdup("paused");
+}
+
+
+/*
+ * return human readable permission string. Never returns NULL.
+ */
+char *cmd_perms_itohuman(unsigned int perms)
+{
+       char *msg = para_malloc(7 * sizeof(char));
+
+       msg[0] = perms & DB_READ? 'd' : '-';
+       msg[1] = perms & DB_WRITE? 'D' : '-';
+       msg[2] = perms & AFS_READ? 'a' : '-';
+       msg[3] = perms & AFS_WRITE? 'A' : '-';
+       msg[4] = '\0';
+       return msg;
+}
+
+/*
+ * Never returns NULL.
+ */
+static char *afs_get_status_flags(unsigned int flags)
+{
+       char *msg = para_malloc(5 * sizeof(char));
+
+       msg[0] = (flags & AFS_PLAYING)? 'P' : '_';
+       msg[1] = (flags & AFS_NOMORE)? 'O' : '_';
+       msg[2] = (flags & AFS_NEXT)? 'N' : '_';
+       msg[3] = (flags & AFS_REPOS)? 'R' : '_';
+       msg[4] = '\0';
+       return msg;
+}
+
+/*
+ * compute status bar string. Never returns NULL
+ */
+char *get_sb_string(struct misc_meta_data *nmmd)
+{
+       char *base, *ret;
+       long long unsigned secs = 0, rsecs = 0, percent = 0;
+
+       base = para_basename(nmmd->filename);
+       if (!base)
+               return para_strdup("");
+       if (!base[0])
+               return base;
+       if (nmmd->chunks_total) {
+               secs = (long long) nmmd->seconds_total * nmmd->chunks_sent
+                       / nmmd->chunks_total;
+               rsecs = (long long) nmmd->seconds_total *
+                       (nmmd->chunks_total - nmmd->chunks_sent)
+                       / nmmd->chunks_total;
+               percent = 100 * ((nmmd->chunks_sent + 5) / 10)
+                       / ((nmmd->chunks_total + 5) / 10);
+       }
+       ret = make_message("%llu:%02llu [%llu:%02llu] (%llu%%) %s",
+               secs / 60, secs % 60,
+               rsecs / 60, rsecs % 60,
+               percent,
+               base
+       );
+       free(base);
+       return ret;
+}
+
+static char *get_status(struct misc_meta_data *nmmd)
+{
+       char *bar, *ret, mtime[30] = "";
+       char *status, *flags; /* afs status info */
+       char *ut = uptime_str();
+       long offset = (nmmd->offset + 500) / 1000;
+       struct timeval now;
+       struct tm mtime_tm;
+
+       if (nmmd->audio_format >= 0) {
+               localtime_r(&nmmd->mtime, &mtime_tm);
+               strftime(mtime, 29, "%a %b %d %Y", &mtime_tm);
+       }
+       /* report real status */
+       status = afs_status_tohuman(nmmd->afs_status_flags);
+       flags = afs_get_status_flags(nmmd->afs_status_flags);
+       bar = para_basename(nmmd->filename);
+       gettimeofday(&now, NULL);
+       ret = make_message(
+               "%s:%lu\n"      "%s:%s\n"               "%s:%i\n"       "%s:%u\n"
+               "%s:%s\n"       "%s:%s\n"       "%s:%s\n"       "%s:%s\n"
+               "%s:%li\n"      "%s:%s\n"       "%s"            "%s"
+               "%s:%s\n"       "%s:%lu.%lu\n"  "%s:%lu.%lu\n",
+               status_item_list[SI_FILE_SIZE], nmmd->size / 1024,
+               status_item_list[SI_MTIME], mtime,
+               status_item_list[SI_LENGTH], nmmd->seconds_total,
+               status_item_list[SI_NUM_PLAYED], nmmd->num_played,
+
+               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_OFFSET], offset,
+               status_item_list[SI_FORMAT], audio_format_name(nmmd->audio_format),
+               nmmd->dbinfo,
+               nmmd->audio_file_info,
+
+               status_item_list[SI_UPTIME], ut,
+               status_item_list[SI_STREAM_START], nmmd->stream_start.tv_sec,
+                       nmmd->stream_start.tv_usec,
+               status_item_list[SI_CURRENT_TIME], now.tv_sec, now.tv_usec
+
+       );
+       free(bar);
+       free(flags);
+       free(status);
+       free(ut);
+       return ret;
+}
+
+static int check_sender_args(int argc, char **argv, struct sender_command_data *scd)
+{
+       int i;
+       /* this has to match sender.h */
+       const char *subcmds[] = {"add", "delete", "allow", "deny", "on", "off", NULL};
+
+       scd->sender_num = -1;
+       if (argc < 0)
+               return -E_COMMAND_SYNTAX;
+       for (i = 0; senders[i].name; i++)
+               if (!strcmp(senders[i].name, argv[0]))
+                       break;
+//     PARA_DEBUG_LOG("%d:%s\n", argc, argv[0]);
+       if (!senders[i].name)
+               return -E_COMMAND_SYNTAX;
+       scd->sender_num = i;
+       for (i = 0; subcmds[i]; i++)
+               if (!strcmp(subcmds[i], argv[1]))
+                       break;
+       if (!subcmds[i])
+               return -E_COMMAND_SYNTAX;
+       scd->cmd_num = i;
+//     scd->self = *in_addr;
+       mmd_lock();
+       if (!senders[scd->sender_num].client_cmds[scd->cmd_num]) {
+               mmd_unlock();
+               return -E_SENDER_CMD;
+       }
+       mmd_unlock();
+       switch (scd->cmd_num) {
+       case SENDER_ON:
+       case SENDER_OFF:
+               if (argc != 1)
+                       return -E_COMMAND_SYNTAX;
+               break;
+       case SENDER_DENY:
+       case SENDER_ALLOW:
+               if (argc != 2 && argc != 3)
+                       return -E_COMMAND_SYNTAX;
+               if (!inet_aton(argv[2], &scd->addr))
+                       return -E_COMMAND_SYNTAX;
+               scd->netmask = 32;
+               if (argc == 3) {
+                       scd->netmask = atoi(argv[3]);
+                       if (scd->netmask < 0 || scd->netmask > 32)
+                               return -E_COMMAND_SYNTAX;
+               }
+               break;
+       case SENDER_ADD:
+       case SENDER_DELETE:
+               if (argc != 2 && argc != 3)
+                       return -E_COMMAND_SYNTAX;
+               if (!inet_aton(argv[2], &scd->addr))
+                       return -E_COMMAND_SYNTAX;
+               scd->port = -1;
+               if (argc == 3) {
+                       scd->port = atoi(argv[3]);
+                       if (scd->port < 0 || scd->port > 65535)
+                               return -E_COMMAND_SYNTAX;
+               }
+               break;
+       default:
+               return -E_COMMAND_SYNTAX;
+       }
+       return 1;
+}
+
+static int com_sender(int fd, int argc, char **argv)
+{
+       int i, ret;
+       struct sender_command_data scd;
+
+       if (!argc) {
+               char *msg = NULL;
+               for (i = 0; senders[i].name; i++) {
+                       char *tmp = make_message("%s%s\n",
+                               msg? msg : "", senders[i].name);
+                       free(msg);
+                       msg = tmp;
+               }
+               ret = send_buffer(fd, msg);
+               free(msg);
+               return ret;
+       }
+       ret = check_sender_args(argc - 1, argv + 1, &scd);
+       if (ret < 0) {
+               char *msg;
+               if (scd.sender_num < 0)
+                       return ret;
+               msg = senders[scd.sender_num].help();
+               send_buffer(fd, msg);
+               free(msg);
+               return 1;
+       }
+       for (i = 0; i < 10; i++) {
+               mmd_lock();
+               if (mmd->sender_cmd_data.cmd_num >= 0) {
+                       mmd_unlock();
+                       usleep(100 * 1000);
+                       continue;
+               }
+               mmd->sender_cmd_data = scd;
+               mmd_unlock();
+               break;
+       }
+       return (i < 10)? 1 : -E_LOCK;
+}
+
+/* server info */
+static int com_si(int fd, int argc, __unused char **argv)
+{
+       int i, ret;
+       char *ut;
+       char *dbtools = 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, " ");
+       }
+       for (i = 0; senders[i].name; i++) {
+               char *info = senders[i].info();
+               sender_info = para_strcat(sender_info, info);
+               free(info);
+               sender_list = para_strcat(sender_list, senders[i].name);
+               sender_list = para_strcat(sender_list, " ");
+       }
+       ut = uptime_str();
+       ret = send_va_buffer(fd, "up: %s\nplayed: %u\n"
+               "pid: %d\n"
+               "mallinfo: %d\n"
+               "connections (active/accepted/total): %u/%u/%u\n"
+               "current loglevel: %i\n"
+               "supported database tools: %s\n"
+               "supported audio formats: %s\n"
+               "supported senders: %s\n"
+               "%s",
+               ut, mmd->num_played,
+               getppid(),
+               mi.arena / 1024,
+               mmd->active_connections,
+               mmd->num_commands,
+               mmd->num_connects,
+               conf.loglevel_arg,
+               dbtools,
+               SUPPORTED_AUDIO_FORMATS,
+               sender_list,
+               sender_info
+       );
+       mmd_unlock();
+       free(ut);
+       free(dbtools);
+       free(sender_list);
+       free(sender_info);
+       return ret;
+}
+
+/* version */
+static int com_version(int socket_fd, int argc, __unused char **argv)
+{
+       if (argc)
+               return -E_COMMAND_SYNTAX;
+       return send_buffer(socket_fd, "para_server-" VERSION ", \"" CODENAME "\"\n"
+                       COPYRIGHT "\n"
+                       "built: " BUILD_DATE "\n"
+                       SYSTEM ", " CC_VERSION "\n"
+               );
+}
+
+/* sc */
+static int com_sc(int socket_fd, int argc, char **argv)
+{
+       char *name = NULL;
+       int ret, old = 0, count = -1; /* print af change forever */
+
+       if (argc)
+               count = atoi(argv[1]);
+repeat:
+       mmd_lock();
+       if (old != mmd->num_played) {
+               old = mmd->num_played;
+               name = para_strdup(mmd->filename);
+       }
+       mmd_unlock();
+       if (name) {
+               ret = send_va_buffer(socket_fd, "%s\n", name);
+               free(name);
+               name = NULL;
+               if (ret < 0)
+                       return ret;
+               if (argc && !--count)
+                       return 1;
+       }
+       usleep(500000);
+       goto repeat;
+}
+
+/* sb */
+static int com_sb(int socket_fd, int argc, char **argv)
+{
+       char *sb;
+       int ret, nr = -1;       /* status bar will be printed that many
+                                * times. Negative value means: print
+                                * forever
+                                */
+       if (argc)
+               nr = atoi(argv[1]);
+       while (nr) {
+               mmd_lock();
+               sb = get_sb_string(mmd);
+               mmd_unlock();
+               ret = send_va_buffer(socket_fd, "%s\n", sb);
+               free(sb);
+               if (ret < 0)
+                       return ret;
+               if (nr == 1)
+                       return 1;
+               usleep(500000);
+               if (nr > 0)
+                       nr--;
+       }
+       return 1;
+}
+
+/* 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
+                         */
+       struct misc_meta_data tmp, *nmmd = &tmp;
+       char *s;
+
+       signal(SIGUSR1, dummy);
+
+       if (argc)
+               num = atoi(argv[1]);
+       for (;;) {
+
+               mmd_dup(nmmd);
+               s = get_status(nmmd);
+               ret = send_buffer(socket_fd, s);
+               free(s);
+               if (ret < 0)
+                       goto out;
+               ret = 1;
+               if (num == 1)
+                       goto out;
+               usleep(500000 * 100);
+       }
+out:
+       return ret;
+}
+
+static int send_description(int fd, struct server_command *cmd, const char *handler, int num)
+{
+       int ret, i;
+
+       for (i = 1; cmd->name && (!num || i <= num); cmd++, i++) {
+               char *perms = cmd_perms_itohuman(cmd->perms);
+               ret = send_va_buffer(fd, "%s\t%s\t%s\t%s\n", cmd->name,
+                       handler,
+                       perms,
+                       cmd->description);
+               free(perms);
+               if (ret < 0)
+                       return ret;
+       }
+       return 1;
+}
+
+/* always returns string that must be freed by the caller in handeler */
+static struct server_command *get_cmd_ptr(char *name, char **handler)
+{
+       struct server_command *cmd = cmd_struct;
+
+       for (cmd = cmd_struct; cmd->name; cmd++)
+               if (!strcmp(cmd->name, name)) {
+                       if (handler)
+                               *handler = para_strdup("para_server"); /* server commands */
+                       return cmd;
+               }
+       /* not found, look for dbtool commands */
+       mmd_lock();
+       if (handler)
+               *handler = make_message("the %s database tool", dblist[mmd->dbt_num].name);
+       cmd = dblist[mmd->dbt_num].cmd_list;
+       mmd_unlock();
+       for (; cmd->name; cmd++)
+               if (!strcmp(cmd->name, name))
+                       return cmd;
+       return NULL;
+}
+
+/* help */
+static int com_help(int fd, int argc, char **argv)
+{
+       struct server_command *cmd;
+       char *perms, *handler;
+       int ret;
+
+       if (!argc) {
+               /* no argument given, print list of commands */
+               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;
+               mmd_unlock();
+               ret = send_description(fd, cmd, handler, 0);
+               free(handler);
+               return ret;
+       }
+       /* argument given for help */
+       cmd = get_cmd_ptr(argv[1], &handler);
+       if (!cmd) {
+               free(handler);
+               return -E_BAD_CMD;
+       }
+       perms = cmd_perms_itohuman(cmd->perms);
+       ret = send_va_buffer(fd,
+               "NAME\n\t%s - %s\n"
+               "SYNOPSIS\n\t para_client %s\n"
+               "DESCRIPTION\n%s\n"
+               "HANDLER\n"
+               "This command is handled by %s.\n\n"
+               "PERMISSIONS\n"
+               "Needed privileges for %s: %s\n",
+               argv[1],
+               cmd->description,
+               cmd->synopsis,
+               cmd->help,
+               handler,
+               argv[1],
+               perms
+       );
+       free(perms);
+       free(handler);
+       return ret;
+}
+
+/* hup */
+static int com_hup(__unused int socket_fd, int argc, __unused char **argv)
+{
+       if (argc)
+               return -E_COMMAND_SYNTAX;
+       kill(getppid(), SIGHUP);
+       return 1;
+}
+
+/* term */
+static int com_term(__unused int socket_fd, int argc, __unused char **argv)
+{
+       if (argc)
+               return -E_COMMAND_SYNTAX;
+       kill(getppid(), SIGTERM);
+       return 1;
+}
+
+static int com_play(__unused int socket_fd, int argc, __unused char **argv)
+{
+       if (argc)
+               return -E_COMMAND_SYNTAX;
+       mmd_lock();
+       mmd->new_afs_status_flags |= AFS_PLAYING;
+       mmd->new_afs_status_flags &= ~AFS_NOMORE;
+       mmd_unlock();
+       return 1;
+
+}
+
+/* stop */
+static int com_stop(__unused int socket_fd, int argc, __unused char **argv)
+{
+       if (argc)
+               return -E_COMMAND_SYNTAX;
+       mmd_lock();
+       mmd->new_afs_status_flags &= ~AFS_PLAYING;
+       mmd->new_afs_status_flags &= ~AFS_REPOS;
+       mmd->new_afs_status_flags |= AFS_NEXT;
+       mmd_unlock();
+       return 1;
+}
+
+/* pause */
+static int com_pause(__unused int socket_fd, int argc, __unused char **argv)
+{
+       if (argc)
+               return -E_COMMAND_SYNTAX;
+       mmd_lock();
+       if (!afs_paused())
+               mmd->events++;
+       mmd->new_afs_status_flags &= ~AFS_PLAYING;
+       mmd->new_afs_status_flags &= ~AFS_NEXT;
+       mmd_unlock();
+       return 1;
+}
+
+static int com_cdt(int fd, int argc, char **argv)
+{
+       int i, ret;
+
+       if (!argc) {
+               char *dbtool;
+               mmd_lock();
+               dbtool = para_strdup(dblist[mmd->dbt_num].name);
+               mmd_unlock();
+               ret = send_va_buffer(fd, "%s\n", dbtool);
+               free(dbtool);
+               return ret;
+       }
+       for (i = 0; dblist[i].name; i++) {
+               if (strcmp(dblist[i].name, argv[1]))
+                       continue;
+               mmd_lock();
+               mmd->dbt_change = i;
+               mmd->events++;
+               mmd_unlock();
+               return 1;
+       }
+       return -E_BAD_DBTOOL;
+}
+
+/* next */
+static int com_next(__unused int socket_fd, int argc, __unused char **argv)
+{
+       if (argc)
+               return -E_COMMAND_SYNTAX;
+       mmd_lock();
+       mmd->new_afs_status_flags |= AFS_NEXT;
+       mmd_unlock();
+       return 1;
+}
+
+/* nomore */
+static int com_nomore(__unused int socket_fd, int argc, __unused char **argv)
+{
+       if (argc)
+               return -E_COMMAND_SYNTAX;
+       mmd_lock();
+       if (afs_playing() || afs_paused())
+               mmd->new_afs_status_flags |= AFS_NOMORE;
+       mmd_unlock();
+       return 1;
+}
+
+/* ff */
+static int com_ff(__unused int socket_fd, int argc, char **argv)
+{
+       long promille;
+       int ret, backwards = 0;
+       unsigned i;
+       char c;
+
+       if (!argc)
+               return -E_COMMAND_SYNTAX;
+       if (!(ret = sscanf(argv[1], "%u%c", &i, &c)))
+               return -E_COMMAND_SYNTAX;
+       if (ret > 1 && c == '-')
+               backwards = 1; /* jmp backwards */
+       mmd_lock();
+       ret = -E_NO_AUDIO_FILE;
+       if (!mmd->chunks_total || !mmd->seconds_total)
+               goto out;
+       promille = (1000 * mmd->current_chunk) / mmd->chunks_total;
+       if (backwards)
+               promille -= 1000 * i / mmd->seconds_total;
+       else
+               promille += 1000 * i / mmd->seconds_total;
+       if (promille < 0)
+               promille = 0;
+       if (promille >  1000) {
+               mmd->new_afs_status_flags |= AFS_NEXT;
+               goto out;
+       }
+       mmd->repos_request = (mmd->chunks_total * promille) / 1000;
+       mmd->new_afs_status_flags |= AFS_REPOS;
+       mmd->new_afs_status_flags &= ~AFS_NEXT;
+       mmd->events++;
+       ret = 1;
+out:
+       mmd_unlock();
+       return ret;
+}
+
+/* jmp */
+static int com_jmp(__unused int socket_fd, int argc, char **argv)
+{
+       long unsigned int i;
+       int ret;
+
+       if (!argc)
+               return -E_COMMAND_SYNTAX;
+       if (sscanf(argv[1], "%lu", &i) <= 0)
+               return -E_COMMAND_SYNTAX;
+       mmd_lock();
+       ret = -E_NO_AUDIO_FILE;
+       if (!mmd->chunks_total)
+               goto out;
+       if (i > 100)
+               i = 100;
+       PARA_INFO_LOG("jumping to %lu%%\n", i);
+       mmd->repos_request = (mmd->chunks_total * i + 50)/ 100;
+       PARA_INFO_LOG("sent: %lu,  offset before jmp: %lu\n",
+               mmd->chunks_sent, mmd->offset);
+       mmd->new_afs_status_flags |= AFS_REPOS;
+       mmd->new_afs_status_flags &= ~AFS_NEXT;
+       ret = 1;
+       mmd->events++;
+out:
+       mmd_unlock();
+       return ret;
+}
+