]> git.tuebingen.mpg.de Git - paraslash.git/commitdiff
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;
+}
+
+/*
+ * check if perms are sufficient to exec a command having perms cmd_perms.
+ * Returns 0 if perms are sufficient, -E_PERM otherwise.
+ */
+static int check_perms(unsigned int perms, struct server_command *cmd_ptr)
+{
+       PARA_DEBUG_LOG("%s", "checking permissions\n");
+       return (cmd_ptr->perms & perms) < cmd_ptr->perms ? -E_PERM : 0;
+}
+
+/*
+ * Parse first string from *cmd and lookup in table of valid commands.
+ * On error, NULL is returned.
+ */
+static struct server_command *parse_cmd(const char *cmdstr)
+{
+       char buf[255];
+       int n = 0;
+
+       sscanf(cmdstr, "%200s%n", buf, &n);
+       if (!n)
+               return NULL;
+       buf[n] = '\0';
+       return get_cmd_ptr(buf, NULL);
+}
+
+long int para_rand(long unsigned max)
+{
+       return (long int) ((max + 0.0) * (random() / (RAND_MAX + 1.0)));
+}
+
+
+/* Open user_list file, returns pointer to opened file on success,
+ * NULL on errors
+ */
+static FILE *open_user_list(char *file)
+{
+       PARA_DEBUG_LOG("opening user list %s\n", file);
+       return fopen(file, "r");
+}
+
+/*
+ * lookup user in user_list file. Fills in a user struct containing
+ * filename of the user's public key as well as the permissions of that user.
+ * Returns 1 on success, 0 if user does not exist and < 0 on errors.
+ */
+static int get_user(struct user *user) {
+       FILE *file_ptr;
+       char *char_ptr;
+       char line[MAXLINE];
+       /* keyword, user, key, perms */
+       char w[MAXLINE], n[MAXLINE], k[MAXLINE], p[MAXLINE], tmp[4][MAXLINE];
+       int num;
+
+       file_ptr = open_user_list(user_list);
+       if (!file_ptr)
+               return -E_USERLIST;
+       while (fgets(line, MAXLINE, file_ptr)) {
+//             PARA_DEBUG_LOG("%s: Read line (%i bytes) "
+//                     "from config file\n", __func__, strlen(line));
+               if (sscanf(line,"%200s %200s %200s %200s", w, n, k, p) < 3)
+                       continue;
+               if (!strcmp(w, "user") && !strcmp(user->name, n)) {
+                       PARA_DEBUG_LOG("found entry for %s\n", n);
+                       strcpy(user->name, n);
+                       strcpy(user->pubkey_file, k);
+                       user->perms = 0;
+                       char_ptr = p;
+                       num = sscanf(char_ptr, "%200[A-Z_],%200[A-Z_],%200[A-Z_],%200[A-Z_]",
+                               tmp[0], tmp[1], tmp[2], tmp[3]);
+                       PARA_DEBUG_LOG("found %i perm entries\n",
+                               num);
+                       user->perms = 0;
+                       while (num > 0) {
+                               num--;
+                               //PARA_DEBUG_LOG("%s: tmp[%i]=%s\n", __func__,
+                               //      num, tmp[num]);
+                               if (!strcmp(tmp[num], "AFS_READ"))
+                                       user->perms =
+                                               user->perms | AFS_READ;
+                               else if (!strcmp(tmp[num], "AFS_WRITE"))
+                                       user->perms =
+                                               user->perms | AFS_WRITE;
+                               else if (!strcmp(tmp[num], "DB_READ"))
+                                       user->perms = user->perms | DB_READ;
+                               else if (!strcmp(tmp[num], "DB_WRITE"))
+                                       user->perms = user->perms | DB_WRITE;
+                               else /* unknown permission */
+                                       PARA_WARNING_LOG("unknown permission:"
+                                               "%s\n", tmp[num]);
+                       }
+                       fclose(file_ptr);
+                       return 1;
+               }
+       }
+       fclose(file_ptr);
+       return 0;
+}
+
+static void init_rc4_keys(void)
+{
+       int i;
+
+       for (i = 0; i < 2 * RC4_KEY_LEN; i++)
+               rc4_buf[i] = para_rand(256);
+       PARA_DEBUG_LOG("rc4 keys initialized (%u:%u)\n",
+               (unsigned char) rc4_buf[0],
+               (unsigned char) rc4_buf[RC4_KEY_LEN]);
+        RC4_set_key(&rc4_recv_key, RC4_KEY_LEN, rc4_buf);
+        RC4_set_key(&rc4_send_key, RC4_KEY_LEN, rc4_buf + RC4_KEY_LEN);
+}
+
+static void rc4_recv(unsigned long len, const unsigned char *indata, unsigned char *outdata)
+{
+        RC4(&rc4_recv_key, len, indata, outdata);
+}
+static void rc4_send(unsigned long len, const unsigned char *indata, unsigned char *outdata)
+{
+        RC4(&rc4_send_key, len, indata, outdata);
+}
+
+
+
+
+int handle_connect(int fd, struct sockaddr_in *addr)
+{
+       int numbytes, ret, argc, use_rc4 = 0;
+       char buf[STRINGSIZE];
+       unsigned char crypt_buf[MAXLINE];
+       struct user u;
+       struct server_command *cmd = NULL;
+       long unsigned challenge_nr, chall_response;
+       char **argv = NULL;
+       char *p, *command = NULL;
+
+       signal(SIGCHLD, SIG_IGN);
+       signal(SIGINT, SIG_DFL);
+       signal(SIGTERM, SIG_DFL);
+       signal(SIGHUP, SIG_DFL);
+       signal(SIGUSR1, SIG_IGN);
+
+       in_addr = addr;
+       challenge_nr = random();
+       /* send Welcome message */
+       ret = send_va_buffer(fd, "This is para_server, version " VERSION  ".\n" );
+       if (ret < 0)
+               goto err_out;
+       /* recv auth request line */
+       ret = recv_buffer(fd, buf, sizeof(buf));
+       if (ret < 0)
+               goto err_out;
+       if (ret <= 6) {
+               ret = -E_AUTH;
+               goto err_out;
+       }
+       numbytes = ret;
+       ret = -E_AUTH;
+       if (strncmp(buf, "auth ", 5))
+               goto err_out;
+
+       if (numbytes < 9 || strncmp(buf, "auth rc4 ", 9))
+               strcpy(u.name, buf + 5); /* client version < 0.2.6 */
+       else {
+               strcpy(u.name, buf + 9); /* client version >= 0.2.6 */
+               use_rc4 = 1;
+       }
+//     strcpy(u.name, buf + 5); /* ok, but ugly */
+       PARA_DEBUG_LOG("received %s request for user %s\n",
+               use_rc4? "rc4" : "auth", u.name);
+       /* lookup user in list file */
+       if ((ret = get_user(&u)) < 0)
+               goto err_out;
+       if (!ret) { /* user not found */
+               PARA_WARNING_LOG("auth request for unknown user %s\n", u.name);
+               ret = -E_BAD_USER;
+               goto err_out;
+       }
+       ret = para_encrypt_challenge(u.pubkey_file, challenge_nr, crypt_buf);
+       if (ret <= 0)
+               goto err_out;
+       numbytes = ret;
+       PARA_DEBUG_LOG("sending %d byte challenge\n", numbytes);
+       /* We can't use send_buffer here since buf may contain null bytes */
+       ret = send_bin_buffer(fd,(char *) crypt_buf, numbytes);
+       if (ret < 0)
+               goto err_out;
+       /* recv decrypted number */
+       numbytes = recv_buffer(fd, buf, sizeof(buf));
+       ret = numbytes;
+       if (ret < 0)
+               goto err_out;
+       ret = -E_AUTH;
+       if (!numbytes)
+               goto err_out;
+       if (sscanf(buf, CHALLENGE_RESPONSE_MSG "%lu", &chall_response) < 1
+                       || chall_response != challenge_nr)
+               goto err_out;
+       /* auth successful. Send 'Proceed' message */
+       PARA_INFO_LOG("good auth for %s (%lu)\n", u.name, challenge_nr);
+       sprintf(buf, "%s", PROCEED_MSG);
+       if (use_rc4) {
+               init_rc4_keys();
+               ret = para_encrypt_buffer(u.pubkey_file, rc4_buf, 2 * RC4_KEY_LEN,
+                       (unsigned char *)buf + PROCEED_MSG_LEN + 1);
+               if (ret <= 0)
+                       goto err_out;
+               numbytes = ret + strlen(PROCEED_MSG) + 1;
+       } else
+               numbytes = strlen(buf);
+       ret = send_bin_buffer(fd, buf, numbytes);
+       if (ret < 0)
+               goto err_out;
+       if (use_rc4) {
+               crypt_function_recv = rc4_recv;
+               crypt_function_send = rc4_send;
+               PARA_INFO_LOG("%s", "rc4 encrytion activated\n");
+       }
+       /* read command */
+       while ((numbytes = recv_buffer(fd, buf, sizeof(buf))) > 0) {
+//             PARA_INFO_LOG("recvd: %s (%d)\n", buf, numbytes);
+               ret = -E_COMMAND_SYNTAX;
+               if (command && numbytes + strlen(command) > STRINGSIZE) /* DOS */
+                       goto err_out;
+               command = para_strcat(command, buf);
+               if ((p = strstr(command, EOC_MSG))) {
+                       *p = '\0';
+                       break;
+               }
+       }
+       ret = numbytes;
+       if (ret < 0)
+               goto err_out;
+       ret = -E_BAD_CMD;
+       /* parse command */
+       if (!(cmd = parse_cmd(command)))
+               goto err_out;
+       /* valid command, check permissions */
+       ret = check_perms(u.perms, cmd);
+       if (ret < 0)
+               goto err_out;
+       /* valid command and sufficient perms */
+       alarm(0);
+       argc = split_args(command, &argv, '\n');
+       argv[0] = cmd->name;
+       mmd_lock();
+       mmd->num_commands++;
+       mmd_unlock();
+       PARA_NOTICE_LOG("calling com_%s() for %s@%s\n", cmd->name, u.name,
+               inet_ntoa(addr->sin_addr));
+       ret = cmd->handler(fd, argc, argv);
+       if (ret >= 0) {
+               ret = EXIT_SUCCESS;
+               goto out;
+       }
+err_out:
+       if (ret != -E_SEND && ret != -E_RECV) {
+               PARA_NOTICE_LOG("%s\n", PARA_STRERROR(-ret));
+               send_va_buffer(fd, "%s\n", PARA_STRERROR(-ret));
+       }
+       ret = EXIT_FAILURE;
+out:
+       free(command);
+       free(argv);
+       mmd_lock();
+       if (cmd && (cmd->perms & DB_WRITE) && ret >= 0)
+               mmd->events++;
+       mmd->active_connections--;
+       mmd_unlock();
+       return ret;
+}
diff --git a/compress.c b/compress.c
new file mode 100644 (file)
index 0000000..da79b72
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ * 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 compress.c paraslash's dynamic audio range compressor */
+
+/*
+ * Based on AudioCompress, (C) 2002-2004  M. Hari Nezumi <magenta@trikuare.cx>
+ */
+
+#include "gcc-compat.h"
+#include "para.h"
+#include "compress_filter.cmdline.h"
+#include "list.h"
+#include "filter.h"
+#include "string.h"
+
+/** how fine-grained the gain is */
+#define GAINSHIFT 10
+/** the size of the output data buffer */
+#define COMPRESS_CHUNK_SIZE 40960
+
+/** data specific to the compress filter */
+struct private_compress_data {
+       /** an array holding the previous peak values */
+       int *peaks;
+       /** current bucket number to be modified */
+       unsigned pn;
+       /** number of times clipping occured */
+       unsigned clip;
+       /** the current multiplier */
+       int current_gain;
+       /** the target multiplier */
+       int target_gain;
+       /** pointer to the configuration data for this instance of the compress filter */
+       struct gengetopt_args_info *conf;
+};
+
+static ssize_t compress(char *inbuf, size_t inbuf_len, struct filter_node *fn)
+{
+       int16_t *audio = (int16_t *) inbuf, *ip = audio, *op;
+       int peak = 1, pos = 0, i, gr, gf, gn;
+       size_t length = MIN((inbuf_len / 2) * 2, (fn->bufsize - fn->loaded) / 2 * 2);
+       struct private_compress_data *pcd = fn->private_data;
+
+       if (!length)
+               return 0;
+       /* determine peak's value and position */
+       for (i = 0; i < length / 2; i++, ip++) {
+               int val = ABS(*ip);
+               if (val > peak) {
+                       peak = val;
+                       pos = i;
+               }
+       }
+       pcd->peaks[pcd->pn] = peak;
+       for (i = 0; i < pcd->conf->buckets_arg; i++) {
+               if (pcd->peaks[i] > peak) {
+                       peak = pcd->peaks[i];
+                       pos = 0;
+               }
+       }
+       /* determine target gain */
+       gn = (1 << GAINSHIFT) * pcd->conf->target_level_arg / peak;
+       if (gn < (1 << GAINSHIFT))
+               gn = 1 << GAINSHIFT;
+       pcd->target_gain = (pcd->target_gain * ((1 << pcd->conf->gain_smooth_arg) - 1) + gn)
+               >> pcd->conf->gain_smooth_arg;
+       /* give it an extra insignificant nudge to counteract possible
+        * rounding error
+        */
+       if (gn < pcd->target_gain)
+               pcd->target_gain--;
+       else if (gn > pcd->target_gain)
+               pcd->target_gain++;
+       if (pcd->target_gain > pcd->conf->gain_max_arg << GAINSHIFT)
+               pcd->target_gain = pcd->conf->gain_max_arg << GAINSHIFT;
+       /* see if a peak is going to clip */
+       gn = (1 << GAINSHIFT) * 32768 / peak;
+       if (gn < pcd->target_gain) {
+               pcd->target_gain = gn;
+               if (pcd->conf->anticlip_given)
+                       pos = 0;
+       } else
+               /* we're ramping up, so draw it out over the whole frame */
+               pos = length;
+       /* determine gain rate necessary to make target */
+       if (!pos)
+               pos = 1;
+       gr = ((pcd->target_gain - pcd->current_gain) << 16) / pos;
+       gf = pcd->current_gain << 16;
+       ip = audio;
+       op = (int16_t *)(fn->buf + fn->loaded);
+       for (i = 0; i < length / 2; i++) {
+               int sample;
+               /* interpolate the gain */
+               pcd->current_gain = gf >> 16;
+               if (i < pos)
+                       gf += gr;
+               else if (i == pos)
+                       gf = pcd->target_gain << 16;
+               /* amplify */
+               sample = (*ip++) * pcd->current_gain * pcd->conf->volume_arg / 10 >> GAINSHIFT;
+               if (sample < -32768) {
+                       pcd->clip++;
+                       sample = -32768;
+               } else if (sample > 32767) {
+                       pcd->clip++;
+                       sample = 32767;
+               }
+               *op++ = sample;
+       }
+       pcd->pn = (pcd->pn + 1) % pcd->conf->buckets_arg;
+       PARA_DEBUG_LOG("bucket: %03i, input len: %i, length: %i, peak: %05i, "
+               "current gain: %03i, clipped: %d\n", pcd->pn, inbuf_len,
+               length, peak, pcd->current_gain, pcd->clip);
+       fn->loaded = length;
+       return length;
+}
+
+static void close_compress(struct filter_node *fn)
+{
+       struct private_compress_data *pcd = fn->private_data;
+       free(pcd->peaks);
+       free(fn->private_data);
+       free(fn->buf);
+}
+
+static void *compress_parse_config(int argc, char **argv)
+{
+       struct gengetopt_args_info *ret = para_calloc(sizeof(struct gengetopt_args_info));
+       if (!compress_cmdline_parser(argc, argv, ret))
+               return ret;
+       free(ret);
+       return NULL;
+}
+
+static void open_compress(struct filter_node *fn)
+{
+       struct private_compress_data *pcd = para_calloc(
+               sizeof(struct private_compress_data));
+//     compress_cmdline_parser(fn->argc, fn->argv, &pcd->conf);
+       pcd->conf = fn->conf;
+       pcd->peaks = para_calloc(pcd->conf->buckets_arg * sizeof(int));
+       fn->private_data = pcd;
+       fn->bufsize = COMPRESS_CHUNK_SIZE;
+       fn->buf = para_malloc(fn->bufsize);
+       fn->loaded = 0;
+}
+
+/** the init function of the compress filter */
+void compress_init(struct filter *f)
+{
+       f->open = open_compress;
+       f->close = close_compress;
+       f->convert = compress;
+       f->print_help = compress_cmdline_parser_print_help;
+       f->parse_config = compress_parse_config;
+}
diff --git a/compress_filter.ggo b/compress_filter.ggo
new file mode 100644 (file)
index 0000000..6a6d763
--- /dev/null
@@ -0,0 +1,7 @@
+section "The dynamic audio range compressor"
+option "anticlip" c "enable clipping protection" flag on
+option "buckets" b "history length" int typestr="number" default="400" no
+option "target_level" t "target signal level (1-32767)" int typestr="number" default="25000" no
+option "gain_max" g "maximum amount to amplify by" int typestr="number" default="4" no
+option "gain_smooth" i "how much inertia ramping has" int typestr="number" default="5" no
+option "volume" v "set soft volume (0-10)" int typestr="number" default="10" no
diff --git a/configure.ac b/configure.ac
new file mode 100644 (file)
index 0000000..da35350
--- /dev/null
@@ -0,0 +1,312 @@
+#                                               -*- Autoconf -*-
+# Process this file with autoconf to produce a configure script.
+
+AC_PREREQ(2.53)
+
+
+AC_INIT(paraslash, [cvs], maan@systemlinux.org)
+AC_CONFIG_HEADER([config.h])
+
+########################################################################### generic
+AC_PROG_CC
+AC_PROG_CPP
+AC_PROG_INSTALL
+
+AC_HEADER_DIRENT
+AC_HEADER_STDC
+AC_HEADER_SYS_WAIT
+AC_CHECK_HEADERS([arpa/inet.h ctype.h fcntl.h limits.h netdb.h netinet/in.h stdlib.h \
+       string.h sys/socket.h sys/time.h sys/timeb.h sys/un.h sys/ipc.h unistd.h utime.h malloc.h], \
+       [], [AC_MSG_ERROR([$ac_header not found])])
+
+AC_CHECK_HEADER(linux/soundcard.h, [extras="$extras para_fade"], 
+       [AC_MSG_WARN([linux/soundcard.h not found, \
+       cannot build para_fade])])
+
+# Checks for typedefs, structures, and compiler characteristics.
+AC_C_CONST
+AC_TYPE_OFF_T
+AC_TYPE_PID_T
+AC_TYPE_SIZE_T
+AC_HEADER_TIME
+AC_STRUCT_TM
+
+# Checks for library functions.
+AC_FUNC_FORK
+AC_PROG_GCC_TRADITIONAL
+AC_FUNC_MALLOC
+AC_FUNC_MEMCMP
+AC_FUNC_MKTIME
+AC_FUNC_MMAP
+AC_FUNC_REALLOC
+AC_FUNC_SELECT_ARGTYPES
+AC_TYPE_SIGNAL
+AC_FUNC_STAT
+AC_FUNC_STRFTIME
+AC_FUNC_VPRINTF
+AC_CHECK_FUNCS([atexit dup2 gethostbyname inet_ntoa memchr memmove memset \
+       regcomp select socket strchr strdup strerror strstr strtol uname], [], 
+       [AC_MSG_ERROR([function not found, cannot live without it])])
+########################################################################### curses
+AC_CHECK_LIB([ncurses], [initscr], [], 
+       [AC_MSG_ERROR([libncurses not found])])
+
+AC_CHECK_LIB([readline], [readline], [], 
+       [AC_MSG_ERROR([libreadline not found])])
+AC_CHECK_LIB([menu], [new_menu], [extras="$extras para_dbadm"],
+       [AC_MSG_WARN([libmenu not found,  cannot build para_dbadm])])
+
+########################################################################### 
+recv_cmdline_objs="recv.cmdline http_recv.cmdline"
+recv_errlist_objs="http_recv recv_common recv time string net"
+recv_ldflags=""
+
+filter_cmdline_objs="filter.cmdline compress_filter.cmdline"
+filter_errlist_objs="filter_chain wav compress filter string"
+filter_ldflags=""
+
+audiod_cmdline_objs="audiod.cmdline grab_client.cmdline compress_filter.cmdline 
+       http_recv.cmdline"
+audiod_errlist_objs="audiod exec close_on_fork signal string daemon stat net 
+       time grab_client filter_chain wav compress http_recv recv_common ringbuffer"
+audiod_ldflags=""
+
+server_cmdline_objs="server.cmdline"
+server_errlist_objs="server mp3 afs command net string signal dopey time daemon stat
+       crypt http_send db close_on_fork"
+server_ldflags=""
+
+########################################################################### ssl
+dnl @synopsis CHECK_SSL
+dnl
+dnl based on the follwing macro from the autoconf archive
+dnl
+dnl @category InstalledPackages
+dnl @author Mark Ethan Trostler <trostler@juniper.net>
+dnl @version 2003-01-28
+dnl @license AllPermissive
+
+AC_DEFUN([CHECK_SSL],
+[
+       for ssldir in $1 /usr/local/ssl /usr/lib/ssl /usr/ssl /usr/pkg /usr/local /usr; do
+               AC_MSG_CHECKING(for openssl in $ssldir)
+               if test -f "$ssldir/include/openssl/ssl.h"; then
+                       found_ssl="yes"
+                       AC_MSG_RESULT(yes)
+                       SSL_CFLAGS="-I$ssldir/include/openssl"
+                       SSL_CPPFLAGS="-I$ssldir/include/openssl"
+                       break
+               fi
+               if test -f "$ssldir/include/ssl.h"; then
+                       found_ssl="yes";
+                       AC_MSG_RESULT(yes)
+                       SSL_CFLAGS="-I$ssldir/include/";
+                       SSL_CPPFLAGS="-I$ssldir/include/";
+                       break
+               fi
+               AC_MSG_RESULT(no)
+       done
+       if test x_$found_ssl != x_yes; then
+               AC_MSG_ERROR(Cannot find ssl libraries)
+       else
+               SSL_LIBS="-lssl -lcrypto";
+               SSL_LDFLAGS="-L$ssldir/lib";
+       fi
+       AC_SUBST(SSL_CPPFLAGS)
+       AC_SUBST(SSL_CFLAGS)
+       AC_SUBST(SSL_LIBS)
+       AC_SUBST(SSL_LDFLAGS)
+])dnl
+
+AC_ARG_ENABLE(ssldir, [AS_HELP_STRING(--enable-ssldir=path,
+                [Search for openssl also in path.])])
+if test "$enable_ssldir" = "yes"; then enable_ssldir=""; fi
+CHECK_SSL($enable_ssldir)
+server_ldflags="$srver_ldflags $SSL_LDFLAGS $SSL_LIBS"
+
+########################################################################### gtk2
+
+pkg_modules="gtk+-2.0 >= 2.0.0"
+PKG_CHECK_MODULES(GTK, [$pkg_modules], [extras="$extras para_krell.so"], [
+       AC_MSG_WARN([gtk+-2 not found, can not build para_krell])
+])
+AC_SUBST(GTK_CFLAGS)
+AC_SUBST(GTK_LIBS)
+
+########################################################################### sdl
+AC_CHECK_LIB([SDL_image], [SDL_Init], [extras="$extras para_sdl_gui"], [
+       AC_MSG_WARN([libSDL_image not found, cannot build para_sdl_gui])
+])
+AC_CHECK_HEADER(SDL/SDL.h, [], 
+       [AC_MSG_WARN([SDL/SDL.h not found])])
+
+########################################################################### mysql
+have_mysql="yes"
+AC_CHECK_HEADER(mysql/mysql.h, [], [
+       have_mysql="no"
+])
+AC_CHECK_LIB([mysqlclient], [mysql_init], [], [
+       have_mysql="no"
+])
+if test "$have_mysql" = "yes"; then
+       server_ldflags="$server_ldflags -lmysqlclient"
+       server_errlist_objs="$server_errlist_objs mysql"
+       AC_DEFINE(HAVE_MYSQL, 1, [define to 1 to turn on mysql support])
+else
+       AC_MSG_WARN([no libmysqlclient, cannot build mysql-based dbtool])
+fi
+########################################################################### ogg
+have_ogg="yes"
+AC_CHECK_LIB([ogg], [ogg_stream_init], [], [
+       have_ogg="no"
+])
+AC_CHECK_LIB([vorbis], [vorbis_info_init], [], [
+       have_ogg="no"
+])
+AC_CHECK_HEADERS([ogg/ogg.h vorbis/codec.h], [], [
+       have_ogg="no"
+])
+if test "$have_ogg" = "yes"; then
+       AC_DEFINE(HAVE_OGGVORBIS, 1, define to 1 to turn on ogg vorbis support)
+       server_ldflags="$server_ldflags -logg -lvorbis -lvorbisfile"
+       filter_ldflags="$filter_ldflags -lvorbis -lvorbisfile"
+       audiod_ldflags="$audiod_ldflags -lvorbis -lvorbisfile"
+
+       filter_cmdline_objs="$filter_cmdline_objs oggdec_filter.cmdline"
+       audiod_cmdline_objs="$audiod_cmdline_objs oggdec_filter.cmdline"
+
+       server_errlist_objs="$server_errlist_objs ogg"
+       filter_errlist_objs="$filter_errlist_objs oggdec"
+       audiod_errlist_objs="$audiod_errlist_objs oggdec"
+else
+       AC_MSG_WARN([no ogg vorbis support in para_server/para_filter])
+fi
+########################################################################### mad
+have_mad="yes"
+AC_CHECK_HEADERS([mad.h], [], [
+       have_mad="no"
+])
+AC_CHECK_LIB([mad], [mad_stream_init], [], [
+       have_mad="no"
+])
+if test "$have_mad" = "yes"; then
+       AC_DEFINE(HAVE_MAD, 1, define to 1 if you want to build the mp3dec filter)
+       filter_errlist_objs="$filter_errlist_objs mp3dec"
+       audiod_errlist_objs="$audiod_errlist_objs mp3dec"
+       filter_ldflags="$filter_ldflags -lmad"
+       audiod_ldflags="$audiod_ldflags -lmad"
+else
+       AC_MSG_WARN([no mp3dec support in para_audiod/para_filter])
+fi
+########################################################################### alsa
+play="para_play"
+msg="will not build para_play"
+AC_CHECK_HEADERS([alsa/asoundlib.h], [], [
+       AC_MSG_WARN([no alsa/asoundlib, $msg])
+       play=""
+])
+AC_CHECK_LIB([asound], [snd_pcm_open], [], [
+       AC_MSG_WARN([no libasound, $msg])
+       play=""
+])
+extras="$extras $play"
+
+
+########################################################################### ortp
+have_ortp="yes"
+pkg_modules="glib-2.0 >= 2.0.4"
+PKG_CHECK_MODULES(GLIB, [$pkg_modules], [], [
+       have_ortp="no"
+])
+CPPFLAGS="$CPPFLAGS $GLIB_CFLAGS"
+AC_CHECK_HEADERS([ortp/ortp.h], [], [
+       have_ortp="no"
+])
+AC_CHECK_LIB([ortp], [ortp_init], [], [
+       have_ortp="no"
+])
+if test "$have_ortp" = "yes"; then
+       recv_cmdline_objs="$recv_cmdline_objs ortp_recv.cmdline"
+       recv_errlist_objs="$recv_errlist_objs ortp_recv"
+
+       audiod_cmdline_objs="$audiod_cmdline_objs ortp_recv.cmdline"
+       audiod_errlist_objs="$audiod_errlist_objs ortp_recv"
+
+       server_errlist_objs="$server_errlist_objs ortp_send"
+
+       recv_ldflags="$recv_ldflags $GLIB_LIBS -lortp"
+       server_ldflags="$server_ldflags $GLIB_LIBS -lortp"
+       audiod_ldflags="$audiod_ldflags $GLIB_LIBS -lortp"
+       AC_DEFINE(HAVE_ORTP, 1, [define to 1 to turn on ortp support])
+
+else
+       AC_MSG_WARN([deactivating ortp support])
+fi
+AC_SUBST(GLIB_CFLAGS)
+AC_SUBST(GLIB_LIBS)
+
+########################################################################### zmw
+slide="para_slider"
+msg="can not build para_slider"
+CPPFLAGS="$GTK_CFLAGS"
+LDFLAGS="$LDFLAGS $GTK_LIBS"
+AC_CHECK_HEADERS([zmw/zmw.h], [], [
+       AC_MSG_WARN([zero memory widget header files not found, $msg])
+       slide=""
+])
+AC_CHECK_LIB([zmw], [zmw_init], [], [
+       AC_MSG_WARN([zero memory widget library not found, $msg])
+       slide=""
+])
+extras="$extras $slide"
+
+
+
+
+
+AC_SUBST(extra_binaries, [$extras])
+AC_SUBST(extra_filter_objs, [$extra_filter_objs])
+AC_SUBST(extra_filter_libs, [$extra_filter_libs])
+AC_SUBST(install_sh, [$INSTALL])
+AC_CONFIG_FILES([Makefile])
+
+
+
+AC_DEFUN([add_dot_o],[$(for i in $@; do printf "$i.o "; done)])
+AC_DEFUN([objlist_to_errlist],[$(for i in $@; do printf "DEFINE_ERRLIST($(echo $i| tr 'a-z' 'A-Z'));"; done) [const char **para_errlist[[]]] = {$(for i in $@; do printf "PARA_ERRLIST($(echo $i | tr 'a-z' 'A-Z')), "; done) }])
+
+recv_objs="$recv_cmdline_objs $recv_errlist_objs"
+filter_objs="$filter_cmdline_objs $filter_errlist_objs"
+audiod_objs="$audiod_cmdline_objs $audiod_errlist_objs"
+server_objs="$server_cmdline_objs $server_errlist_objs"
+
+AC_SUBST(recv_objs, add_dot_o($recv_objs))
+AC_SUBST(recv_ldflags, $recv_ldflags)
+AC_DEFINE_UNQUOTED(INIT_RECV_ERRLISTS, objlist_to_errlist($recv_errlist_objs),
+       errors used by para_recv)
+
+AC_SUBST(filter_objs, add_dot_o($filter_objs))
+AC_SUBST(filter_ldflags, $filter_ldflags)
+AC_DEFINE_UNQUOTED(INIT_FILTER_ERRLISTS, 
+       objlist_to_errlist($filter_errlist_objs), errors used by para_filter)
+
+AC_SUBST(audiod_objs, add_dot_o($audiod_objs))
+AC_SUBST(audiod_ldflags, $audiod_ldflags)
+AC_DEFINE_UNQUOTED(INIT_AUDIOD_ERRLISTS, objlist_to_errlist($audiod_errlist_objs),
+       errors used by para_audiod)
+
+AC_SUBST(server_objs, add_dot_o($server_objs))
+AC_SUBST(server_ldflags, $server_ldflags)
+AC_DEFINE_UNQUOTED(INIT_SERVER_ERRLISTS, 
+       objlist_to_errlist($server_errlist_objs), errors used by para_server)
+AC_OUTPUT
+AC_MSG_NOTICE([creating Makefile.deps])
+gcc -MM -MG *.c > Makefile.deps
+AC_MSG_NOTICE([
+paraslash configuration:
+~~~~~~~~~~~~~~~~~~~~~~~~
+mysql support: $have_mysql
+ogg vorbis support: $have_ogg
+mp3dec support (libmad): $have_mad
+ortp support: $have_ortp
+])
diff --git a/crypt.c b/crypt.c
new file mode 100644 (file)
index 0000000..34407d7
--- /dev/null
+++ b/crypt.c
@@ -0,0 +1,159 @@
+/*
+ * 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 crypt.c openssl-based RSA encryption/decryption routines */
+
+#include <openssl/pem.h>
+#include "para.h"
+#include "error.h"
+#include "string.h"
+
+/** \cond used to distinguish between loading of private/public key */
+#define LOAD_PUBLIC_KEY 0
+#define LOAD_PRIVATE_KEY 1
+/** \endcond **/
+
+static EVP_PKEY *load_key(const char *file, int private)
+{
+       BIO *key;
+       EVP_PKEY *pkey = NULL;
+
+       key = BIO_new(BIO_s_file());
+       if (!key)
+               return NULL;
+       if (BIO_read_filename(key, file) > 0) {
+               if (private == LOAD_PRIVATE_KEY)
+                       pkey = PEM_read_bio_PrivateKey(key, NULL, NULL, NULL);
+               else
+                       pkey = PEM_read_bio_PUBKEY(key, NULL, NULL, NULL);
+       }
+       BIO_free(key);
+       return pkey;
+}
+
+static int get_key(char *key_file, RSA **rsa, int private)
+{
+       EVP_PKEY *key = load_key(key_file, private);
+
+       if (!key)
+               return (private == LOAD_PRIVATE_KEY)? -E_PRIVATE_KEY : -E_PUBLIC_KEY;
+       *rsa = EVP_PKEY_get1_RSA(key);
+       EVP_PKEY_free(key);
+       if (!*rsa)
+               return -E_RSA;
+       return RSA_size(*rsa);
+}
+
+/**
+ * decrypt a buffer using an RSA key
+ *
+ * \param key_file full path of the rsa key
+ * \param outbuf the output buffer
+ * \param inbuf the encrypted input buffer
+ * \param rsa_inlen the length of \a inbuf
+ *
+ * The \a outbuf must be large enough to hold at least \a rsa_inlen bytes.
+ *
+ * \return The size of the recovered plaintext on success, negative on errors.
+ *
+ * \sa RSA_private_decrypt(3)
+ **/
+int para_decrypt_buffer(char *key_file, unsigned char *outbuf, unsigned char *inbuf,
+                       int rsa_inlen)
+{
+       RSA *rsa;
+       int ret = get_key(key_file, &rsa, LOAD_PRIVATE_KEY);
+
+       if (ret < 0)
+               return ret;
+       ret = RSA_private_decrypt(rsa_inlen, inbuf, outbuf, rsa, RSA_PKCS1_PADDING);
+       return (ret > 0)? ret : -E_DECRYPT;
+}
+
+/**
+ * decrypt the challenge number sent by para_server
+ *
+ * \param key_file full path of the rsa key
+ * \param challenge_nr result is stored here
+ * \param inbuf the input buffer
+ * \param rsa_inlen the length of \a inbuf
+ *
+ * \return positive on success, negative on errors
+ *
+ * \sa para_decrypt_buffer()
+ */
+int para_decrypt_challenge(char *key_file, long unsigned *challenge_nr,
+               unsigned char *inbuf, int rsa_inlen)
+{
+       unsigned char *rsa_out = OPENSSL_malloc(128);
+       int ret = para_decrypt_buffer(key_file, rsa_out, inbuf, rsa_inlen);
+
+       if (ret >= 0)
+               ret = sscanf((char *)rsa_out, "%lu", challenge_nr) == 1?
+                       1 : -E_CHALLENGE;
+       OPENSSL_free(rsa_out);
+       return ret;
+}
+
+/**
+ * encrypt a buffer using an RSA key
+ *
+ * \param key_file full path of the rsa key
+ * \param inbuf the input buffer
+ * \param len the length of \a inbuf
+ * \param outbuf the output buffer
+ *
+ * \return The size of the encrypted data on success, negative on errors
+ *
+ * \sa RSA_public_encrypt(3)
+ */
+int para_encrypt_buffer(char *key_file, unsigned char *inbuf,
+               const unsigned len, unsigned char *outbuf)
+{
+       RSA *rsa;
+       int ret = get_key(key_file, &rsa, LOAD_PUBLIC_KEY);
+
+       if (ret < 0)
+               return ret;
+       ret = RSA_public_encrypt(len, inbuf, outbuf, rsa, RSA_PKCS1_PADDING);
+       return ret < 0?  -E_ENCRYPT : ret;
+}
+
+/**
+ * encrypt the given challenge number
+ *
+ * \param key_file full path of the rsa key
+ * \param challenge_nr the number to be encrypted
+ * \param outbuf the output buffer
+ *
+ * \a outbuf must be at least 64 bytes long
+ *
+ * \return The size of the encrypted data on success, negative on errors
+ *
+ * \sa para_encrypt_buffer()
+ *
+ */
+int para_encrypt_challenge(char *key_file, long unsigned challenge_nr,
+       unsigned char *outbuf)
+{
+       unsigned char *inbuf = (unsigned char*) make_message("%lu", challenge_nr);
+       int ret = para_encrypt_buffer(key_file, inbuf, strlen((char *)inbuf), outbuf);
+       free(inbuf);
+       return ret;
+}
+
diff --git a/crypt.h b/crypt.h
new file mode 100644 (file)
index 0000000..82ed4f9
--- /dev/null
+++ b/crypt.h
@@ -0,0 +1,9 @@
+/** \file crypt.h prototypes for the RSA crypt functions */
+int para_decrypt_challenge(char *key_file, long unsigned *challenge_nr,
+       unsigned char *buf, int rsa_inlen);
+int para_encrypt_challenge(char *key_file, long unsigned challenge_nr,
+       unsigned char *outbuf);
+int para_encrypt_buffer(char *key_file, unsigned char *inbuf, const unsigned len,
+       unsigned char *outbuf);
+int para_decrypt_buffer(char *key_file, unsigned char *outbuf, unsigned char *inbuf,
+       int rsa_inlen);
diff --git a/daemon.c b/daemon.c
new file mode 100644 (file)
index 0000000..616fd2c
--- /dev/null
+++ b/daemon.c
@@ -0,0 +1,173 @@
+/*
+ * 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 daemon.c some helpers for programs that detach from the console */
+#include "para.h"
+#include "daemon.h"
+#include <pwd.h>
+#include "string.h"
+
+/**
+ * do the usual stuff to become a daemon
+ *
+ * Fork, become session leader, dup fd 0, 1, 2 to /dev/null.
+ *
+ * \sa fork(2), setsid(2), dup(2)
+ */
+void daemon_init(void)
+{
+       pid_t pid;
+       int null;
+
+       PARA_INFO_LOG("%s", "daemonizing\n");
+       if ((pid = fork()) < 0) {
+               PARA_EMERG_LOG("%s", "failed to init daemon\n");
+               exit(EXIT_FAILURE);
+       } else if (pid)
+               exit(EXIT_SUCCESS); /* parent exits */
+       /* child */
+       setsid(); /* become session leader */
+       chdir("/");
+       umask(0);
+
+       null = open("/dev/null", O_RDONLY);
+       if (null < 0)
+               exit(EXIT_FAILURE);
+       dup2(null, STDIN_FILENO);
+       dup2(null, STDOUT_FILENO);
+       dup2(null, STDERR_FILENO);
+       close(null);
+}
+
+/**
+ * fopen() a file in append mode
+ *
+ * \param logfile_name the name of the file to open
+ *
+ * Either calls exit() or returns a valid file handle.
+ */
+/* might be called from para_log, so we must not use para_log */
+FILE *open_log(const char *logfile_name)
+{
+       FILE *logfile;
+
+       if (!logfile_name)
+               return NULL;
+       if (!(logfile = fopen(logfile_name, "a")))
+               exit(EXIT_FAILURE);
+       setlinebuf(logfile);
+       return logfile;
+}
+
+/**
+ * close the log file of the daemon
+ *
+ * \param logfile the log file handle
+ *
+ * It's OK to call this with logfile == NULL
+ */
+void close_log(FILE* logfile)
+{
+       if (!logfile)
+               return;
+       PARA_INFO_LOG("%s", "closing logfile\n");
+       fclose(logfile);
+}
+
+/**
+ * log the startup message containing the paraslash version
+ */
+void log_welcome(const char *whoami, int loglevel)
+{
+       PARA_INFO_LOG("welcome to %s " VERSION " ("BUILD_DATE")\n", whoami);
+       PARA_DEBUG_LOG("using loglevel %d\n", loglevel);
+}
+
+/**
+ * give up superuser privileges
+ *
+ * This function returns immediately if not invoked with EUID zero.  Otherwise,
+ * it tries to obtain the UID for the user specified by \a username and exits
+ * if this user was not found. On success, effective and real UID and the saved
+ * set-user-ID are all set to the UID of \a username.
+ *
+ * \sa getpwnam(3), getuid(2), setuid(2)
+ */
+void para_drop_privileges(const char *username)
+{
+       struct passwd *p;
+       char *tmp;
+
+       if (geteuid())
+               return;
+       if (!username) {
+               PARA_EMERG_LOG("%s", "root privileges, but no user option given\n");
+               exit(EXIT_FAILURE);
+       }
+       tmp = para_strdup(username);
+       p = getpwnam(tmp);
+       free(tmp);
+       if (!p) {
+               PARA_EMERG_LOG("%s", "no such user\n");
+               exit(EXIT_FAILURE);
+       }
+       PARA_NOTICE_LOG("%s", "dropping root privileges\n");
+       setuid(p->pw_uid);
+       PARA_DEBUG_LOG("uid: %d, euid: %d\n", getuid(), geteuid());
+       setuid(p->pw_uid);
+}
+
+/**
+ * set/get the server uptime
+ *
+ * \param set_or_get chose one of the two modes
+ *
+ * This should be called at startup time with \a set_or_get equal to \p
+ * UPTIME_SET which sets the uptime to zero.  Subsequent calls with \a
+ * set_or_get equal to \p UPTIME_GET return the number of seconds ellapsed
+ * since the last reset.
+ *
+ * \sa time(2), difftime(3)
+ */
+time_t server_uptime(enum uptime set_or_get)
+{
+       static time_t startuptime;
+       time_t now;
+
+       if (set_or_get == UPTIME_SET) {
+               time(&startuptime);
+               return 0;
+       }
+       time(&now);
+       return (time_t) difftime(now, startuptime);
+}
+
+/**
+ * construct string containing uptime
+ *
+ * The format of the returned string is "days:hours:minutes"
+ *
+ * \sa server_uptime
+ */
+__malloc char *uptime_str(void)
+{
+       time_t t = server_uptime(UPTIME_GET);
+       return make_message("%li:%02li:%02li", t / 86400,
+               (t / 3600) % 24, (t / 60) % 60);
+}
+
diff --git a/daemon.h b/daemon.h
new file mode 100644 (file)
index 0000000..11035b5
--- /dev/null
+++ b/daemon.h
@@ -0,0 +1,12 @@
+/** \file daemon.h exported symbols from daemon.c */
+
+
+void daemon_init(void);
+FILE *open_log(const char *logfile_name);
+void close_log(FILE* logfile);
+void log_welcome(const char *whoami, int loglevel);
+void para_drop_privileges(const char *username);
+/** used for server_uptime() */
+enum uptime {UPTIME_SET, UPTIME_GET};
+time_t server_uptime(enum uptime set_or_get);
+__malloc char *uptime_str(void);
diff --git a/db.c b/db.c
new file mode 100644 (file)
index 0000000..d97a6e0
--- /dev/null
+++ b/db.c
@@ -0,0 +1,128 @@
+/*
+ * 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 db.c functions common to all database tools. */
+
+#include "server.cmdline.h"
+#include "server.h"
+#include "afs.h"
+#include <dirent.h> /* readdir() */
+#include <sys/stat.h> /* stat */
+#include <sys/types.h> /* mode_t */
+#include "error.h"
+#include "string.h"
+
+/*
+ * return 1 if name matches any supported audio format
+ */
+static int match_audio_file_name(char *name)
+{
+       int i, len = strlen(name);
+       const char *pattern[] = {SUPPORTED_AUDIO_FORMATS_ARRAY};
+
+       for (i = 0; pattern[i]; i++) {
+               const char *p = pattern[i];
+               int plen = strlen(p);
+               if (len < plen + 1)
+                       continue;
+               if (name[len - plen - 1] != '.')
+                       continue;
+               if (strcasecmp(name + len - plen, p))
+                       continue;
+               return 1;
+       }
+       return 0;
+}
+
+/**
+ * traverse the given directory recursively
+ *
+ * @param dirname the directory to traverse
+ * @param f: the function to call for each entry.
+ *
+ * for each regular file whose filename ends in .yyy, where yyy is a supported
+ * audio format, the supplied function \a f is called.  The directory and
+ * filename component of the regular file are passed to \a f.
+ *
+ * \return On success, 1 is returned. Otherwise, this function returns a
+ * negative value which indicates the kind of the error.
+ */
+int find_audio_files(const char *dirname, int (*f)(const char *, const char *))
+{
+       DIR *dir = NULL;
+       struct dirent *entry;
+       /*
+        * Opening the current directory (".") and calling fchdir() to return
+        * is usually faster and more reliable than saving cwd in some buffer
+        * and calling chdir() afterwards (see man 3 getcwd).
+        */
+       char cwd_fd = open(".", O_RDONLY);
+       struct stat s;
+       int ret = -1;
+
+//     PARA_DEBUG_LOG("dirname: %s\n", dirname);
+       if (cwd_fd < 0)
+               return -E_GETCWD;
+       ret = -E_CHDIR;
+       if (chdir(dirname) < 0)
+               goto out;
+       ret = -E_OPENDIR;
+       dir = opendir(".");
+       if (!dir)
+               goto out;
+       /* scan cwd recursively */
+       while ((entry = readdir(dir))) {
+               mode_t m;
+               char *tmp;
+
+               if (!strcmp(entry->d_name, "."))
+                       continue;
+               if (!strcmp(entry->d_name, ".."))
+                       continue;
+               ret = -E_LSTAT;
+               if (lstat(entry->d_name, &s) == -1)
+                       goto out;
+               m = s.st_mode;
+               if (!S_ISREG(m) && !S_ISDIR(m)) /* skip links, sockets, ... */
+                       continue;
+               if (S_ISREG(m)) { /* regular file */
+                       if (!match_audio_file_name(entry->d_name))
+                               continue;
+                       if (f(dirname, entry->d_name) < 0)
+                               goto out;
+                       continue;
+               }
+               /* directory */
+               tmp = make_message("%s/%s", dirname, entry->d_name);
+               ret = find_audio_files(tmp, f);
+               free(tmp);
+               if (ret < 0)
+                       goto out;
+       }
+       ret = 1;
+out:
+       if (dir)
+               closedir(dir);
+       if (fchdir(cwd_fd) < 0)
+               ret = -E_CHDIR;
+       close(cwd_fd);
+       if (ret < 0)
+               PARA_ERROR_LOG("ret = %x\n", -ret);
+       return ret;
+}
diff --git a/db.h b/db.h
new file mode 100644 (file)
index 0000000..960bf59
--- /dev/null
+++ b/db.h
@@ -0,0 +1,95 @@
+/*
+ * 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 db.h data structures common to all database tools */
+
+int find_audio_files(const char *dirname, int (*f)(const char *, const char *));
+
+/**
+ * describes one of para_server's supported database tools
+ *
+ * There is exactly one such struct for each supported database tool.  During
+ * the startup part of para_server the \a init() function of the activated
+ * database tool gets called which fills in the other members.
+
+ *
+ *
+ */
+struct dbtool {
+/**
+ * name name of this database tool
+ */
+const char *name;
+/**
+ * the database init routine
+ *
+ * It should check its command line options and do all necessary initialization
+ * like connecting to a database server.
+ *
+ * A negative return value indicates an initialization error and means that
+ * this database tool should be ignored for now (it may later be activated
+ * again via the cdt command).
+ *
+ * If \a init() returns success (non-negative return value), it must have
+ * initialized in all non-optional function pointers of the given dbtool
+ * struct. Moreover, \a cmd_list must point to a NULL-terminated array which
+ * holds the list of all commands that are supported by this database tool.
+ */
+int (*init)(struct dbtool *self);
+/**
+ * list of commands supported by this dbtool
+ */
+struct server_command *cmd_list;
+/**
+ * pointer to function returning list of at most \a num audio files to be
+ * streamed next
+ *
+ * \a get_audio_file_list() must return a pointer to a array of at most \a num
+ * char* pointers (terminated by a NULL pointer), or NULL on errors.  Both the
+ * array and its contents must be dynamically allocated and are freed by the
+ * caller.
+ *
+*/
+char **(*get_audio_file_list)(unsigned int num);
+/**
+ *
+ * the update hook
+ *
+ * The \a update_audio_file pointer is optional and need not be supplied. In this
+ * case it is not neccessary to init this pointer from within init(). If
+ * \a update_audio_file is non-NULL, the function it points to gets called
+ * whenever a new audio file was successfully loaded and is going to be
+ * streamed by any of paraslash's senders. The full path of the audio file is
+ * passed \a update_audio_file().
+ *
+ */
+void  (*update_audio_file)(char *audio_file);
+/**
+ *
+ * shutdown this database tool and free all resources
+ *
+ * This gets called whenever the database tool changes (via the cdt command),
+ * or when para_server receives the HUP signal, or when para_server shuts down.
+ * It is assumed to succeed.
+*/
+void (*shutdown)(void);
+};
+
+int mysql_dbtool_init(struct dbtool*);
+int dopey_dbtool_init(struct dbtool*);
+
diff --git a/dbadm.c b/dbadm.c
new file mode 100644 (file)
index 0000000..5f807eb
--- /dev/null
+++ b/dbadm.c
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2004-2006 Andre Noll <maan@systemlinux.org>
+ *
+ *     This program is free software; you can redistribute it and/or modify
+ *     it under the terms of the GNU General Public License as published by
+ *     the Free Software Foundation; either version 2 of the License, or
+ *     (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU General Public License for more details.
+ *
+ *     You should have received a copy of the GNU General Public License
+ *     along with this program; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ */
+
+/** \file dbadm.c simple attribute setting utility for the mysql dbtool */
+
+#include "para.h"
+#include <menu.h>
+#include "string.h"
+
+//#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
+#define MAXLINE 255
+
+#define OFFSET 4
+static int att_win_lines, att_win_cols;
+static int att_format_lines, att_format_cols;
+static int atts_modified, refresh_file = 1;
+static int n_choices, choice_len;
+static char *atts;
+ITEM **my_items;
+WINDOW *att_win;
+
+static char **choices;
+
+/* no looging */
+void para_log(__unused int ll, __unused char *fmt,...)
+{
+}
+
+static int client_cmd(const char *cmd)
+{
+       pid_t pid;
+       int ret, fds[3] = {0, 1, 0};
+       char *cmdline = make_message(BINDIR "/para_client %s", cmd);
+       ret = para_exec_cmdline_pid(&pid, cmdline, fds);
+       free(cmdline);
+       if (ret < 0)
+               return -1;
+       return fds[1];
+}
+
+static char **get_all_atts(int *num_atts)
+{
+       int fd = client_cmd("laa");
+       FILE *pipe;
+       char **ret = NULL, *buf;
+
+       if (fd < 0)
+               return NULL;
+       pipe = fdopen(fd, "r");
+       if (!pipe) {
+               close(fd);
+               return NULL;
+       }
+       *num_atts = 0;
+       buf = para_malloc(MAXLINE * sizeof(char));
+       while (fgets(buf, MAXLINE - 1, pipe) && *buf) {
+               size_t n = strlen(buf);
+               buf[n - 1] = '\0';
+               if (choice_len < n - 1)
+                       choice_len = n - 1;
+               ret = para_realloc(ret, (*num_atts + 1) * sizeof(char*));
+               ret[*num_atts] = para_strdup(buf);
+               *num_atts += 1;
+       }
+       free(buf);
+       return ret;
+}
+
+static char *get_atts(char *filename)
+{
+       int n, fd, bufsize = (n_choices * (choice_len + 1) + 10) * sizeof(char);
+       char *cmd = make_message("la %s", filename), *buf;
+
+       fd = client_cmd(cmd);
+       free(cmd);
+       if (fd < 0)
+               return NULL;
+       buf = para_malloc(bufsize * sizeof(char));
+       n = read(fd, buf, bufsize - 1);
+       if (n <= 0 ||strstr(buf, "Not contained in database")) {
+               free(buf);
+               return NULL;
+       };
+       return buf;
+}
+
+static void _item_init(__unused MENU* menu)
+{
+//     static int subsequent_run;
+       int i, n;
+       char *p = atts;
+       char att[MAXLINE];
+
+       if (!refresh_file)
+               return;
+       refresh_file = 0;
+       for (i = 0; i < n_choices; i++)
+               set_item_value(my_items[i], FALSE);
+       while (sscanf(p, "%s%n", att, &n) > 0) {
+               //mvprintw(LINES - 4, 0, "aaaitem.");
+               p += n + 1;
+               for (i = 0; i < n_choices; i++) {
+                       if (!strcmp(item_name(my_items[i]), att)) {
+                               set_item_value(my_items[i], TRUE);
+                               break;
+                       }
+               }
+       }
+}
+
+struct color_pair {
+       int bg;
+       int fg;
+};
+
+enum {
+       COLOR_DUMMY,
+       COLOR_FRAME,
+       COLOR_FILENAME,
+       COLOR_ACTIVE_ITEM,
+       COLOR_INACTIVE_ITEM
+};
+
+static void init_colors(void)
+{
+       start_color();
+       init_pair(COLOR_FRAME, COLOR_BLUE, COLOR_BLACK);
+       init_pair(COLOR_FILENAME, COLOR_MAGENTA, COLOR_BLACK);
+       init_pair(COLOR_ACTIVE_ITEM, COLOR_RED, COLOR_WHITE);
+       init_pair(COLOR_INACTIVE_ITEM, COLOR_WHITE, COLOR_BLACK);
+}
+
+static int commit_changes(char *filename)
+{
+//     ITEM **items;
+       int i;
+       char buf[MAXLINE] = "para_client sa ";
+
+       for (i = 0; i < n_choices; ++i) {
+               strcat(buf, item_name(my_items[i]));
+               if (item_value(my_items[i])) {
+       //              printf("%s\n", item_name(my_items[i]));
+                       strcat(buf, "+ ");
+               } else
+                       strcat(buf, "- ");
+       }
+       strcat(buf, filename);
+       //printf("old atts: %s\n", atts);
+       //printf("%s\n", buf);
+       return system(buf);
+}
+
+static char *get_current_filename(void)
+{
+       char *bn = NULL, *buf = para_malloc(MAXLINE * sizeof(char));
+       int ret, fd;
+
+       fd = client_cmd("sc 1");
+       if (fd < 0)
+               return NULL;
+       ret = read(fd, buf, MAXLINE - 1);
+       if (ret <= 0)
+               goto out;
+       buf[ret] = '\0';
+       bn = para_basename(buf);
+       free(buf);
+out:
+       close(fd);
+       return bn;
+}
+
+static void print_filename(char *filename)
+{
+       char *tmp = strdup(filename);
+       int maxlen = att_win_cols - 2;
+
+       wattron(att_win, COLOR_PAIR(COLOR_FILENAME));
+       if (strlen(filename) > maxlen)
+               tmp[maxlen] = '\0';
+       wmove(att_win, 1, 1);
+       clrtoeol();
+       mvwprintw(att_win, 1, 1, "%s", tmp);
+       wattron(att_win, COLOR_PAIR(COLOR_FRAME));
+       mvwaddch(att_win, 2, att_win_cols - 1, ACS_RTEE);
+       free(tmp);
+}
+
+static int com_refresh_file(char *filename) {
+
+
+       filename = get_current_filename();
+       if (!filename)
+               return -1;
+       atts = get_atts(filename);
+       if (!atts)
+               return -1;
+       print_filename(filename);
+       return 1;
+}
+
+static int init_curses(void)
+{
+       /* Initialize curses */
+       initscr();
+
+       if (LINES <= OFFSET + 4)
+               return -1;
+
+       att_format_cols = (COLS - 2 * OFFSET - 3) / (choice_len + 1);
+               if (att_format_cols < 1)
+                       return -1;
+       att_format_lines = (n_choices - 1) / att_format_cols + 1;
+       if (att_format_lines + OFFSET + 4 > LINES)
+               att_format_lines = LINES - OFFSET - 4;
+
+       att_win_lines = att_format_lines + 4;
+       att_win_cols = (choice_len + 1) * att_format_cols + 1;
+       if (att_win_lines + OFFSET > LINES)
+               att_win_lines = LINES - OFFSET - 1;
+       if (att_win_cols + 2 * OFFSET > COLS)
+               att_win_cols = COLS - 2 * OFFSET + 1;
+       //printf ("%i:%i, %i:%i\n", att_format_lines, att_format_cols, att_win_lines, att_win_cols); fflush(stdout); sleep(2);
+
+        cbreak();
+        noecho();
+       keypad(stdscr, TRUE);
+       init_colors();
+       return 1;
+}
+
+int main(int argc, char *argv[])
+{
+       int c, i, ret = EXIT_FAILURE;
+       MENU *my_menu = NULL;
+       char *filename;
+
+       choices = get_all_atts(&n_choices);
+       if (!choices ||  n_choices <= 0)
+               exit(EXIT_FAILURE);
+       if (argc < 2) {
+               filename = get_current_filename();
+               if (!filename)
+                       exit(EXIT_FAILURE);
+       } else
+               filename = strdup(argv[1]);
+       atts = get_atts(filename);
+       if (!atts)
+               goto out;
+       if (init_curses() < 0)
+               goto out;
+
+       /* Initialize items */
+        my_items = (ITEM **)calloc(n_choices + 1, sizeof(ITEM *));
+        for (i = 0; i < n_choices; ++i)
+                my_items[i] = new_item(choices[i], "");
+
+       my_menu = new_menu(my_items);
+       set_item_init(my_menu, _item_init);
+       /* Make the menu multi valued */
+       menu_opts_off(my_menu, O_ONEVALUE);
+       /* Set menu option not to show the description */
+       menu_opts_off(my_menu, O_SHOWDESC);
+
+       /* Create the window to be associated with the menu */
+       att_win = newwin(att_win_lines, att_win_cols, OFFSET, OFFSET);
+       keypad(att_win, TRUE);
+       /* Set main window and sub window */
+        set_menu_win(my_menu, att_win);
+       set_menu_sub(my_menu, derwin(att_win, att_win_lines - 4,
+               att_win_cols - 2, 3, 1));
+       set_menu_format(my_menu, att_format_lines, att_format_cols);
+       //set_menu_format(my_menu, 5, 1);
+       set_menu_mark(my_menu, "");
+
+       /* Print a border around the main window and print a title */
+       wattron(att_win, COLOR_PAIR(COLOR_FRAME));
+       box(att_win, 0, 0);
+       mvwhline(att_win, 2, 1, ACS_HLINE , att_win_cols - 2);
+       mvwaddch(att_win, 2, 0, ACS_LTEE);
+       print_filename(filename);
+       set_menu_fore(my_menu, COLOR_PAIR(COLOR_ACTIVE_ITEM) | A_REVERSE);
+       set_menu_back(my_menu, COLOR_PAIR(COLOR_INACTIVE_ITEM));
+       refresh();
+       post_menu(my_menu);
+
+repeat:
+       wrefresh(att_win);
+       c = getch();
+       switch(c) {
+               case KEY_DOWN:
+                       menu_driver(my_menu, REQ_DOWN_ITEM);
+                       goto repeat;
+               case KEY_UP:
+                       menu_driver(my_menu, REQ_UP_ITEM);
+                       goto repeat;
+               case KEY_LEFT:
+                       menu_driver(my_menu, REQ_LEFT_ITEM);
+                       goto repeat;
+               case KEY_RIGHT:
+                       menu_driver(my_menu, REQ_RIGHT_ITEM);
+                       goto repeat;
+
+               case 'q':
+                       ret = EXIT_SUCCESS;
+                       goto out;
+               case 'r':
+                       com_refresh_file(filename);
+                       refresh_file = 1;
+                       _item_init(NULL);
+                       goto repeat;
+
+               case ' ':
+                       menu_driver(my_menu, REQ_TOGGLE_ITEM);
+                       atts_modified = 1;
+                       goto repeat;
+               /* Enter */
+               case 10:
+                       endwin();
+                       if (atts_modified)
+                               commit_changes(filename);
+                       else
+                               printf("Attributes unchanged\n");
+                       goto out;
+               default:
+                       goto repeat;
+       }
+out:
+       if (my_items) {
+               free_item(my_items[0]);
+               free_item(my_items[1]);
+       }
+       for (i = 0; i < n_choices; i++)
+               free(choices[i]);
+       free(choices);
+       if (my_menu)
+               free_menu(my_menu);
+       if (atts)
+               free(atts);
+       if (filename)
+               free(filename);
+       endwin();
+       exit(ret);
+}
+
diff --git a/dopey.c b/dopey.c
new file mode 100644 (file)
index 0000000..91e1368
--- /dev/null
+++ b/dopey.c
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2004-2006 Andre Noll <maan@systemlinux.org>
+ *
+ *     This program is free software; you can redistribute it and/or modify
+ *     it under the terms of the GNU General Public License as published by
+ *     the Free Software Foundation; either version 2 of the License, or
+ *     (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU General Public License for more details.
+ *
+ *     You should have received a copy of the GNU General Public License
+ *     along with this program; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ */
+
+/** \file dopey.c Simple database tool implementation. Feel free to modify.  */
+
+#include <sys/time.h> /* gettimeofday */
+#include "server.cmdline.h"
+#include "server.h"
+#include "db.h"
+#include "error.h"
+#include "net.h"
+#include "string.h"
+
+static int com_dopey(int, int, char **);
+extern struct gengetopt_args_info conf;
+extern struct misc_meta_data *mmd;
+
+static unsigned int num_audio_files, audio_file_count;
+static char **audio_file_list;
+
+static int count_audio_files(__unused const char *dir, __unused const char *name)
+{
+       num_audio_files++;
+       return 1;
+}
+
+static int remember_file(const char *dir, const char *name)
+{
+       if (audio_file_count >= num_audio_files)
+               return -E_FILE_COUNT;
+       audio_file_list[audio_file_count] = make_message("%s/%s", dir, name);
+       audio_file_count++;
+       return 1;
+}
+
+/* array of commands that are supported by this database tool */
+static struct server_command cmds[] = {
+{
+.name = "dopey",
+.handler = com_dopey,
+.perms = 0,
+.description = "about the dopey database tool",
+.synopsis = "dopey",
+.help =
+
+"It's so dumb. It hurts. Don't use it; switch to the mysql database\n"
+"tool instead. OTOH: You typed 'help dopey', so if you serious about\n"
+"that and you really intend to help the dopey database tool, look at\n"
+"my source code, dopey.c, and modify it to make it something useful.\n"
+
+}, {
+.name = NULL,
+}
+};
+
+static int com_dopey(int fd, __unused int argc, __unused char *argv[])
+{
+       return send_buffer(fd, "Please do not use me. I'm too sick to do "
+               "anything for you. Switch me off. Now!\n");
+}
+
+/*
+ * Load a list of all audio files into memory and chose num of them randomly.
+ * Called by server to determine next audio file to be streamed.
+ */
+static char **dopey_get_audio_file_list(unsigned int num)
+{
+       int i, ret;
+       unsigned int len;
+       char **ret_list = NULL; /* what we are going to return */
+
+       audio_file_list = NULL;
+       num_audio_files = 0;
+       /* first run, just count all audio files. dopey */
+       ret = find_audio_files(conf.dopey_dir_arg, count_audio_files);
+       if (ret < 0)
+               goto out;
+       ret = -E_NOTHING_FOUND;
+       if (!num_audio_files)
+               goto out;
+       /* yeah, that doesn't scale, also dopey */
+       audio_file_list = para_malloc(num_audio_files * sizeof(char *));
+       audio_file_count = 0;
+       /* second run (hot dentry cache, hopefully), fill audio_file_list */
+       ret = find_audio_files(conf.dopey_dir_arg, remember_file);
+       if (ret < 0)
+               goto out;
+       /* careful, files might got deleted underneath */
+       num_audio_files = audio_file_count; /* can only decrease */
+       len = MIN(num, num_audio_files);
+       ret = -E_NOTHING_FOUND;
+       if (!len) /* nothing found, return NULL */
+               goto out;
+       /* success, return NULL-terminated list */
+       ret_list = para_calloc((len + 1) * sizeof(char *));
+       for (i = 0; i < len; i++) { /* choose randomly */
+               int r = (int) ((num_audio_files + 0.0) * (rand()
+                       / (RAND_MAX + 1.0)));
+               ret_list[i] = para_strdup(audio_file_list[r]);
+       }
+out:
+       if (audio_file_list) {
+               for (i = 0; i < num_audio_files; i++)
+                       free(audio_file_list[i]);
+               free(audio_file_list);
+       }
+//     if (ret < 0)
+//             PARA_ERROR_LOG("%s\n", PARA_STRERROR(-ret));
+       return ret_list;
+}
+
+static void dopey_shutdown(void)
+{
+       PARA_DEBUG_LOG("%s", "thanks for using another dbtool.\n");
+}
+
+/** dopey's (constant) database info text */
+#define DBINFO "dbinfo1:database info? You're kidding. I'm dopey!\ndbinfo2:\ndbinfo3:\n"
+
+/** the dopey init function
+ *
+ * Init all function pointers of \a db, init the dbinfo text and seed the
+ * PRNG.
+ *
+ * \sa struct dbtool, misc_meta_data::dbinfo, mysql.c
+ */
+int dopey_dbtool_init(struct dbtool *db)
+{
+       struct timeval now;
+
+       PARA_INFO_LOG("%s", "registering dopey handlers\n");
+       sprintf(mmd->dbinfo, DBINFO);
+       gettimeofday(&now, NULL);
+       srand(now.tv_usec);
+       db->cmd_list = cmds;
+       db->get_audio_file_list = dopey_get_audio_file_list;
+       db->shutdown = dopey_shutdown;
+       return 1;
+}
diff --git a/error.h b/error.h
new file mode 100644 (file)
index 0000000..bc8475e
--- /dev/null
+++ b/error.h
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2006 Andre Noll <maan@systemlinux.org>
+ *
+ *     This program is free software; you can redistribute it and/or modify
+ *     it under the terms of the GNU General Public License as published by
+ *     the Free Software Foundation; either version 2 of the License, or
+ *     (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU General Public License for more details.
+ *
+ *     You should have received a copy of the GNU General Public License
+ *     along with this program; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ */
+
+/** \file error.h list of error messages for all subsystems */
+
+/** \cond list of all subsystems that support the shiny new error facility */
+enum para_subsystem {SS_RECV,
+       SS_NET, SS_ORTP_RECV, SS_AUDIOD, SS_EXEC, SS_CLOSE_ON_FORK, SS_SIGNAL,
+       SS_STRING, SS_DAEMON, SS_STAT, SS_TIME, SS_GRAB_CLIENT, SS_HTTP_RECV,
+       SS_RECV_COMMON, SS_FILTER_CHAIN, SS_WAV, SS_COMPRESS, SS_OGGDEC, SS_FILTER,
+       SS_COMMAND, SS_DOPEY, SS_CRYPT, SS_HTTP_SEND, SS_ORTP_SEND, SS_DB, SS_OGG,
+       SS_MP3, SS_MP3DEC, SS_SERVER, SS_AFS, SS_MYSQL, SS_RINGBUFFER};
+#define NUM_SS (SS_RINGBUFFER + 1)
+extern const char **para_errlist[];
+/** \endcond */
+
+#define NET_ERRORS \
+       PARA_ERROR(SEND, "send error"), \
+       PARA_ERROR(RECV, "receive error"), \
+       PARA_ERROR(SOCKET, "socket error"), \
+       PARA_ERROR(CONNECT, "connect error"), \
+       PARA_ERROR(ACCEPT, "accept error"), \
+       PARA_ERROR(SETSOCKOPT, "failed to set socket options"), \
+       PARA_ERROR(BIND, "bind error"), \
+       PARA_ERROR(NAME_TOO_LONG, "name too long for struct sockaddr_un"), \
+       PARA_ERROR(CHMOD, "failed to set socket mode"), \
+       PARA_ERROR(SENDMSG, "sendmsg() failed"), \
+       PARA_ERROR(RECVMSG, "recvmsg() failed"), \
+       PARA_ERROR(SCM_CREDENTIALS, "did not receive SCM credentials"), \
+       PARA_ERROR(LISTEN, "listen error"), \
+       PARA_ERROR(RECV_PATTERN, "did not receive expected pattern"), \
+       PARA_ERROR(HOST_INFO, "gethostbyname() failed"), \
+
+
+#define ORTP_RECV_ERRORS \
+       PARA_ERROR(MSG_TO_BUF, "failed to extract rtp packet"), \
+       PARA_ERROR(ORTP_SYNTAX, "ottp syntax error"), \
+       PARA_ERROR(TOO_MANY_BAD_CHUNKS, "too many consecutive bad chunks"), \
+       PARA_ERROR(INVALID_HEADER, "invalid header packet"), \
+       PARA_ERROR(OVERRUN, "outout buffer overrun"), \
+
+
+#define HTTP_RECV_ERRORS \
+       PARA_ERROR(SEND_HTTP_REQUEST, "failed to send http request"), \
+       PARA_ERROR(MISSING_OK, "did not receive OK message from peer"), \
+       PARA_ERROR(HTTP_RECV_BUF, "did not receive buffer")
+
+
+#define RECV_ERRORS \
+       PARA_ERROR(RECV_SELECT, "recv select error"), \
+       PARA_ERROR(WRITE_STDOUT, "stdout write error"), \
+
+
+#define RECV_COMMON_ERRORS \
+       PARA_ERROR(RECV_SYNTAX, "recv syntax error"), \
+
+
+#define AUDIOD_ERRORS \
+       PARA_ERROR(WRITE_AUDIO_DATA, "failed to write audio data"), \
+       PARA_ERROR(NO_MORE_SLOTS, "no more empty slots"), \
+       PARA_ERROR(MISSING_COLON, "syntax error: missing colon"), \
+       PARA_ERROR(UNSUPPORTED_AUDIO_FORMAT, "given audio format not supported"), \
+       PARA_ERROR(CLIENT_WRITE, "client write error"), \
+       PARA_ERROR(UCRED_PERM, "permission denied"), \
+       PARA_ERROR(INVALID_AUDIOD_CMD, "invalid command"), \
+
+
+#define FILTER_CHAIN_ERRORS \
+       PARA_ERROR(UNSUPPORTED_FILTER, "given filter not supported"), \
+       PARA_ERROR(BAD_FILTER_OPTIONS, "invalid filter option given"), \
+
+
+#define STAT_ERRORS \
+       PARA_ERROR(TOO_MANY_CLIENTS, "maximal number of stat clients exceeded"), \
+       PARA_ERROR(UNKNOWN_STAT_ITEM, "status item not recognized"), \
+
+
+#define OGGDEC_ERRORS \
+       PARA_ERROR(OGGDEC_READ, "read from media returned an error"), \
+       PARA_ERROR(OGGDEC_NOTVORBIS, "bitstream is not vorbis data"), \
+       PARA_ERROR(OGGDEC_VERSION, "vorbis version mismatch"), \
+       PARA_ERROR(OGGDEC_BADHEADER, "invalid vorbis bitstream header"), \
+       PARA_ERROR(OGGDEC_FAULT, "bug or heap/stack corruption"), \
+       PARA_ERROR(OGGDEC_BADLINK, "invalid stream section or requested link corrupt"), \
+
+
+#define GRAB_CLIENT_ERRORS \
+       PARA_ERROR(PEDANTIC_GRAB, "fd not ready and pedantic grab requested"), \
+       PARA_ERROR(GC_WRITE, "grab client write error"), \
+       PARA_ERROR(INVALID_GRAB_MODE, "invalid grab client mode"), \
+       PARA_ERROR(BAD_GC_SLOT, "invalid slot requested by grab client"), \
+       PARA_ERROR(BAD_GC_FILTER_NUM, "invalid filter number given"), \
+       PARA_ERROR(GC_SYNTAX, "grab client syntax error"), \
+       PARA_ERROR(GC_HELP_GIVEN, ""), /* not really an error */ \
+
+
+#define MP3DEC_ERRORS \
+       PARA_ERROR(MAD_FRAME_DECODE, "mad frame decode error"), \
+       PARA_ERROR(MP3DEC_OVERRUN, "mp3 output buffer overrun"), \
+
+
+#define FILTER_ERRORS \
+       PARA_ERROR(NO_FILTERS, "at least one filter must be given"), \
+       PARA_ERROR(FILTER_SYNTAX, "syntax error"), \
+
+
+#define SIGNAL_ERRORS \
+       PARA_ERROR(SIGNAL_SIG_ERR, "signal() retured SIG_ERR"), \
+       PARA_ERROR(SIGNAL_READ, "read error from signal pipe"), \
+       PARA_ERROR(WAITPID, "waitpid error"), \
+
+
+#define STRING_ERRORS \
+       PARA_ERROR(MKSTEMP, "mkstemp error: unable to create tmp file"), \
+       PARA_ERROR(FCHMOD, "fchmod error: can not set mode"), \
+
+
+#define EXEC_ERRORS \
+       PARA_ERROR(DUP_PIPE, "exec error: can not create pipe"), \
+       PARA_ERROR(NULL_OPEN, "can not open /dev/null"), \
+
+
+#define MP3_ERRORS \
+       PARA_ERROR(FREAD, "fread error"), \
+       PARA_ERROR(FSEEK, "fseek error"), \
+       PARA_ERROR(FRAME, "invalid mp3 frame"), \
+       PARA_ERROR(FRAME_LENGTH, "invalid frame length"), \
+       PARA_ERROR(MP3_NO_FILE, "invalid mp3 file pointer"), \
+       PARA_ERROR(MP3_INFO, "could not read mp3 info"), \
+       PARA_ERROR(MP3_REPOS, "mp3 repositioning error"), \
+       PARA_ERROR(HEADER_FREQ, "invalid header frequency"), \
+       PARA_ERROR(HEADER_BITRATE, "invalid header bitrate"), \
+
+
+#define OGG_ERRORS \
+       PARA_ERROR(OGG_READ, "ogg read error"), \
+       PARA_ERROR(SYNC_PAGEOUT, "ogg sync page-out error (no ogg file?)"), \
+       PARA_ERROR(STREAM_PAGEIN, "ogg stream page-in error (first page)"), \
+       PARA_ERROR(STREAM_PACKETOUT, "ogg stream packet-out error (first packet)"), \
+       PARA_ERROR(VORBIS, "vorbis synthesis header-in error (not vorbis?)"), \
+       PARA_ERROR(OGG_NO_FILE, "invalid ogg file pointer"), \
+       PARA_ERROR(OGG_OPEN, "ov_open error"), \
+       PARA_ERROR(OGG_INFO, "ov_info error"), \
+       PARA_ERROR(OGG_REPOS, "ogg repositioning error"), \
+
+
+#define AFS_ERRORS \
+       PARA_ERROR(AUDIO_FORMAT, "audio format not recognized"), \
+       PARA_ERROR(FSTAT, "failed to fstat() audio file"), \
+
+
+#define DB_ERRORS \
+       PARA_ERROR(GETCWD, "can not get current working directory"), \
+       PARA_ERROR(CHDIR, "can not change directory"), \
+       PARA_ERROR(OPENDIR, "can not open directory"), \
+       PARA_ERROR(LSTAT, "lstat error"), \
+
+
+#define CRYPT_ERRORS \
+       PARA_ERROR(PRIVATE_KEY, "can not read private key"), \
+       PARA_ERROR(PUBLIC_KEY, "can not read public key"), \
+       PARA_ERROR(RSA, "RSA error"), \
+       PARA_ERROR(ENCRYPT, "encrypt error"), \
+       PARA_ERROR(DECRYPT, "decrypt error"), \
+       PARA_ERROR(CHALLENGE, "failed to read challenge"), \
+
+
+#define HTTP_SEND_ERRORS \
+       PARA_ERROR(QUEUE, "packet queue overrun"), \
+       PARA_ERROR(WRITE_OK, "can not check whether fd is writable"), \
+
+
+#define DOPEY_ERRORS \
+       PARA_ERROR(FILE_COUNT, "audio file count exceeded"), \
+       PARA_ERROR(NOTHING_FOUND, "no audio files found"), \
+
+
+#define MYSQL_ERRORS \
+       PARA_ERROR(MYSQL_SYNTAX, "mysql syntax error"), \
+       PARA_ERROR(NOTCONN, "not connected to mysql server"), \
+       PARA_ERROR(TOOBIG, "mysql: file too large"), \
+       PARA_ERROR(NAMETOOLONG, "mysql: name too long"), \
+       PARA_ERROR(QFAILED, "mysql query failed"), \
+       PARA_ERROR(NOROW, "row is NULL"), \
+       PARA_ERROR(NOATTS, "can not get attributes from mysql table"), \
+       PARA_ERROR(NORESULT, "error while fetching mysql result"), \
+       PARA_ERROR(EMPTY_RESULT, "result is empty"), \
+       PARA_ERROR(ESCAPE, "can not escape string"), \
+       PARA_ERROR(GET_AUDIO_FILE, "can not get current audio file"), \
+       PARA_ERROR(GET_STREAM, "can not get current stream"), \
+       PARA_ERROR(NO_STREAM, "no such stream"), \
+       PARA_ERROR(GET_QUERY, "can not get query for specified stream"), \
+       PARA_ERROR(TMPFILE, "error while writing temporary file"), \
+       PARA_ERROR(META, "can not get meta data"), \
+       PARA_ERROR(MYSQL_INIT, "can not initialize mysql connection"), \
+       PARA_ERROR(NO_MYSQL_PASSWD, "fatal: no mysql passord given"), \
+       PARA_ERROR(NO_AF_DIR, "fatal: audio file directory not given"), \
+
+
+#define COMMAND_ERRORS \
+       PARA_ERROR(COMMAND_SYNTAX, "syntax error in command"), \
+       PARA_ERROR(AUTH, "did not receive auth request"), \
+       PARA_ERROR(BAD_DBTOOL, "no such database tool"), \
+       PARA_ERROR(NO_AUDIO_FILE, "no audio file"), \
+       PARA_ERROR(BAD_CMD, "invalid command"), \
+       PARA_ERROR(PERM, "permission denied"), \
+       PARA_ERROR(USERLIST, "failed to open user list file"), \
+       PARA_ERROR(BAD_USER, "you don't exist. Go away."), \
+       PARA_ERROR(LOCK, "lock error"), \
+       PARA_ERROR(SENDER_CMD, "command not supported by this sender"), \
+
+
+/* these do not need error handling (yet) */
+#define SERVER_ERRORS
+#define WAV_ERRORS
+#define COMPRESS_ERRORS
+#define TIME_ERRORS
+#define CLOSE_ON_FORK_ERRORS
+#define DAEMON_ERRORS
+#define ORTP_SEND_ERRORS
+#define RINGBUFFER_ERRORS
+
+
+/**
+ * the subsystem shift
+ *
+ * 255 error codes ought to be enough for every subsystem. Use the higher bits
+ * of the return value to encode the subsystem number
+ */
+#define SS_SHIFT 8
+
+/**
+ * compute the subsystem offset
+ *
+ * It is given by x * 2**8 where \a x is the subsystem number
+ */
+#define SS_OFFSET(ss) (SS_ ## ss << SS_SHIFT)
+
+/**
+ * make the enum of all errors of one subsystem
+ *
+ * As zero should not be an error, we define a dummy enum entry with value
+ * 2**ss. That lets the real errors start at 2**ss + 1.
+ */
+#define SS_ENUM(ss) enum {\
+       E_ ## ss ## _DUMMY = SS_OFFSET(ss), \
+       ss ## _ERRORS}
+
+/**
+ * determine the subsystem number from the error number
+ *
+ * Easy, it's just \a num / 2**8.
+ */
+#define ERRNUM_TO_SS(num) ((num) >> SS_SHIFT)
+
+/**
+ * determine the index of an error number
+ *
+ * Also easy: It's the lower 8 bits of num - 1.
+ */
+#define ERRNUM_TO_INDEX(num) (((1 << SS_SHIFT) - 1) & ((num) - 1))
+
+/**
+ * paraslash's version of strerror(3)
+ *
+ * expands to the error text of \a num (a string constant).
+ */
+#define PARA_STRERROR(num) para_errlist[ERRNUM_TO_SS(num)] [ERRNUM_TO_INDEX(num)]
+
+/**
+ * define the error list for one subsystem
+ *
+ * Used by macros in config.h (generated by configure)
+ */
+#define DEFINE_ERRLIST(ss) const char * ss ## _ERRLIST[] = {ss ## _ERRORS}
+
+/**
+ * activate errors for one subsystem.
+ *
+ * Each executable needs only the error lists of those subssystems it is actually
+ * linked with. We always reserve zeroed-out space for NUM_SS char ** pointers, but
+ * only init those of the needed subsystems. This macro is used by macros in config.h
+ * (generated by configure).
+ */
+#define PARA_ERRLIST(ss) [SS_ ## ss] = ss ## _ERRLIST
+
+/**
+ * This is temporarily defined to expand to its second argument (prefixed by
+ * 'E_') and gets later redefined to expand to the error text only
+ */
+#define PARA_ERROR(err, msg) E_ ## err
+
+/** \cond popcorn time */
+SS_ENUM(ORTP_RECV);
+SS_ENUM(NET);
+SS_ENUM(RECV);
+SS_ENUM(AUDIOD);
+SS_ENUM(EXEC);
+SS_ENUM(CLOSE_ON_FORK);
+SS_ENUM(SIGNAL);
+SS_ENUM(STRING);
+SS_ENUM(DAEMON);
+SS_ENUM(STAT);
+SS_ENUM(TIME);
+SS_ENUM(GRAB_CLIENT);
+SS_ENUM(HTTP_RECV);
+SS_ENUM(RECV_COMMON);
+SS_ENUM(FILTER_CHAIN);
+SS_ENUM(WAV);
+SS_ENUM(COMPRESS);
+SS_ENUM(OGGDEC);
+SS_ENUM(MP3DEC);
+SS_ENUM(FILTER);
+SS_ENUM(MP3);
+SS_ENUM(OGG);
+SS_ENUM(SERVER);
+SS_ENUM(AFS);
+SS_ENUM(COMMAND);
+SS_ENUM(DOPEY);
+SS_ENUM(CRYPT);
+SS_ENUM(HTTP_SEND);
+SS_ENUM(ORTP_SEND);
+SS_ENUM(DB);
+SS_ENUM(MYSQL);
+SS_ENUM(RINGBUFFER);
+/** \endcond */
+#undef PARA_ERROR
+/* rest of the world only sees the error text */
+#define PARA_ERROR(err, msg) msg
diff --git a/exec.c b/exec.c
new file mode 100644 (file)
index 0000000..34643b8
--- /dev/null
+++ b/exec.c
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2003-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 exec.c helper functions for spawning new processes */
+#include "para.h"
+#include "close_on_fork.h"
+#include "error.h"
+#include "string.h"
+
+/**
+ * spawn a new process and redirect fd 0, 1, and 2
+ *
+ * \param pid will hold the pid of the created process upon return
+ * \param file path of the executable to execute
+ * \param args the argument array for the command
+ * \param fds a pointer to a value-result array
+ *
+ * This function uses fork/exec to create a new process.  \a fds must be a
+ * pointer to three integers, corresponding to stdin, stdout and stderr
+ * respectively. It specifies how to deal with fd 0, 1, 2 in the child.  The
+ * contents of \a fds are interpreted as follows:
+ *
+ *     - fd[i] < 0: leave fd \a i alone
+ *     - fd[i] = 0: dup fd \a i to /dev/null
+ *     - fd[i] > 0: create a pipe and dup i to one end of that pipe.
+ *     Upon return, fd[i] contains the file descriptor of the pipe.
+ *
+ *     In any case, all unneeded filedescriptors are closed.
+ *
+ * \return Negative on errors, positive on success.
+ *
+ * \sa null(4), pipe(2), dup2(2), fork(2), exec(3)
+ */
+int para_exec(pid_t *pid, const char *file, char *const args[], int *fds)
+{
+       int ret, in[2] = {-1, -1}, out[2] = {-1, -1}, err[2] = {-1, -1},
+               null = -1; /* ;) */
+
+       ret = -E_DUP_PIPE;
+       if (fds[0] > 0 && pipe(in) < 0)
+               goto err_out;
+       if (fds[1] > 0 && pipe(out) < 0)
+               goto err_out;
+       if (fds[2] > 0 && pipe(err) < 0)
+               goto err_out;
+       if (!fds[0] || !fds[1] || !fds[2]) {
+               ret = -E_NULL_OPEN;
+               null = open("/dev/null", O_RDONLY);
+               if (null < 0)
+                       goto err_out;
+       }
+       if ((*pid = fork()) < 0)
+               exit(EXIT_FAILURE);
+       if (!(*pid)) { /* child */
+               close_listed_fds(); /* close unneeded fds */
+               if (fds[0] >= 0) {
+                       if (fds[0]) {
+                               close(in[1]);
+                               if (in[0] != STDIN_FILENO)
+                                       dup2(in[0], STDIN_FILENO);
+                       } else
+                               dup2(null, STDIN_FILENO);
+               }
+               if (fds[1] >= 0) {
+                       if (fds[1]) {
+                               close(out[0]);
+                               if (out[1] != STDOUT_FILENO)
+                                       dup2(out[1], STDOUT_FILENO);
+                       } else
+                               dup2(null, STDOUT_FILENO);
+               }
+               if (fds[2] >= 0) {
+                       if (fds[2]) {
+                               close(err[0]);
+                               if (err[1] != STDERR_FILENO)
+                                       dup2(err[1], STDERR_FILENO);
+                       } else
+                               dup2(null, STDERR_FILENO);
+               }
+               if (null >= 0)
+                       close(null);
+               execvp(file, args);
+               _exit(EXIT_FAILURE);
+       }
+       if (fds[0] > 0) {
+               close(in[0]);
+               *fds = in[1];
+       }
+       if (fds[1] > 0) {
+               close(out[1]);
+               *(fds + 1) = out[0];
+       }
+       if (fds[2] > 0) {
+               close(err[1]);
+               *(fds + 2) = err[0];
+       }
+       if (null >= 0)
+               close(null);
+       return 1;
+err_out:
+       if (err[0] >= 0)
+               close(err[0]);
+       if (err[1] >= 0)
+               close(err[1]);
+       if (out[0] >= 0)
+               close(out[0]);
+       if (out[1] >= 0)
+               close(out[1]);
+       if (in[0] >= 0)
+               close(in[0]);
+       if (in[1] >= 0)
+               close(in[1]);
+       if (null >= 0)
+               close(null);
+       return ret;
+}
+
+
+/**
+ * exec the given command
+ *
+ * \param pid the same meaning as in para_exec()
+ * \param cmdline holds the command and its arguments, seperated by spaces
+ * \param fds the same meaning as in para_exec()
+ *
+ * A wrapper for para_exec() which calls split_args() to seperate
+ * the command line arguments.
+ *
+ * \return positive on success, negative on errors
+ */
+int para_exec_cmdline_pid(pid_t *pid, char *cmdline, int *fds)
+{
+       int argc, ret;
+       char **argv, *tmp = para_strdup(cmdline);
+
+       if (!tmp)
+               exit(EXIT_FAILURE);
+       argc = split_args(tmp, &argv, ' ');
+       ret = para_exec(pid, argv[0], argv, fds);
+       free(argv);
+       free(tmp);
+       return ret;
+}
+
+/**
+ * check whether a file exists
+ *
+ * \param fn the file name
+ *
+ * \return Non-zero iff file exists.
+ */
+int file_exists(const char *fn)
+{
+       struct stat statbuf;
+
+       return !stat(fn, &statbuf);
+}
diff --git a/fade.c b/fade.c
new file mode 100644 (file)
index 0000000..1f66b36
--- /dev/null
+++ b/fade.c
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 1998-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.
+ */
+
+/** \file fade.c a volume fader and alarm clock */
+
+#include "fade.cmdline.h"
+#include "para.h"
+
+#include <ctype.h>
+#include <curses.h>
+#include <stdlib.h> /* EXIT_SUCCESS */
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <limits.h>
+#include <linux/soundcard.h>
+#include "string.h"
+
+
+struct gengetopt_args_info args_info;
+
+void para_log(__unused int ll, char *fmt,...)
+{
+       va_list argp;
+       time_t t1;
+       struct tm *tm;
+
+       time(&t1);
+       tm = localtime(&t1);
+       printf("%d:%02d:%02d ", tm->tm_hour, tm->tm_min, tm->tm_sec);
+       va_start(argp, fmt);
+       vprintf(fmt, argp);
+       va_end(argp);
+}
+
+/*
+ * open mixer device
+ */
+static int open_mixer(void)
+{
+        int mixer_fd;
+
+        if ((mixer_fd = open(args_info.mixer_device_arg, O_RDWR, 0)) < 0)
+                return -1;
+        return mixer_fd;
+}
+
+/*
+ * get volume via mixer_fd
+ */
+static int do_get_vol(int mixer_fd)
+{
+       int volume;
+
+       if (ioctl(mixer_fd, MIXER_READ(SOUND_MIXER_VOLUME), &volume) < 0)
+               return -1;
+       /* take the mean value of left and right volume */
+       return (volume % 256 + (volume >> 8)) / 2;
+}
+
+/*
+ * open mixer, get volume and close mixer
+ */
+static int get_vol(void)
+{
+       int mixer_fd;
+       int volume;
+
+       mixer_fd = open_mixer();
+       if (mixer_fd < 0)
+               return -1;
+       volume = do_get_vol(mixer_fd);
+       close(mixer_fd);
+       return volume;
+}
+
+/*
+ * set volume via mixer_fd
+ */
+static int do_set_vol(int mixer_fd, int volume)
+{
+       int tmp;
+       tmp = (volume << 8) + volume;
+       if (ioctl(mixer_fd, MIXER_WRITE(SOUND_MIXER_VOLUME), &tmp) < 0)
+               return 0;
+       return 1;
+}
+
+/*
+ * open mixer, set volume and close mixer
+ */
+static int set_vol(int volume)
+{
+       int mixer_fd;
+       int ret;
+
+       mixer_fd = open_mixer();
+       ret = 0;
+       if (mixer_fd < 0)
+               goto out;
+       if (!do_set_vol(mixer_fd, volume))
+               goto out;
+       ret = 1;
+       close(mixer_fd);
+out:
+       return ret;
+}
+
+/*
+ * Open mixer, get volume, fade to new_vol in secs seconds and
+ * close mixer
+ */
+static int fade(int new_vol, unsigned int secs)
+{
+       int vol, mixer_fd = -1, diff, incr, ret;
+       struct timespec ts;
+       unsigned long long tmp, tmp2; /* Careful with that axe, Eugene! */
+
+       PARA_NOTICE_LOG("fading to %d in %d seconds\n", new_vol, secs);
+       ret = 0;
+       if (!secs)
+               goto out;
+       mixer_fd = open_mixer();
+       if (mixer_fd < 0)
+               goto out;
+       vol = do_get_vol(mixer_fd);
+       if (vol < 0)
+               goto out;
+       diff = new_vol - vol;
+       if (!diff) {
+               sleep(secs);
+               ret = 1;
+               goto out;
+       }
+       incr = diff > 0? 1: -1;
+       diff = diff > 0? diff: -diff;
+       tmp = secs * 1000 / diff;
+       tmp2 = tmp % 1000;
+       while ((new_vol - vol) * incr > 0) {
+               ts.tv_nsec = tmp2 * 1000000; /* really nec ?*/
+               ts.tv_sec = tmp / 1000; /* really nec ?*/
+               //printf("ts.tv_sec: %i\n", ts.tv_nsec);
+               vol += incr;
+               if (!do_set_vol(mixer_fd, vol))
+                       goto out;
+               //printf("vol = %i\n", vol);
+               nanosleep(&ts, NULL);
+       }
+out:
+       if (mixer_fd >= 0)
+               close(mixer_fd);
+       return 1;
+}
+
+static int client_cmd(char *cmd,...)
+{
+       int ret, fds[3] = {0, 0, 0};
+        pid_t pid;
+       char *cmdline = make_message(BINDIR "/para_client %s", cmd);
+       PARA_INFO_LOG("%s", cmdline);
+       ret = para_exec_cmdline_pid(&pid, cmdline, fds);
+       free(cmdline);
+       return ret;
+}
+
+/*
+ * sleep
+ */
+static void sweet_dreams(void)
+{
+       time_t t1, wake_time_epoch;
+       unsigned int delay;
+       struct tm *tm;
+       int hour = args_info.wake_hour_arg;
+       int min = args_info.wake_min_arg;
+       char *fa_stream = args_info.fa_stream_arg;
+       char *wake_stream = args_info.wake_stream_arg;
+       //char *current_stream = stat_items[STREAM].content;
+       int wf = args_info.wake_fade_arg;
+       int sf = args_info.fa_fade_arg;
+       int wv = args_info.wake_vol_arg;
+       int sv = args_info.fa_vol_arg;
+       int iv = args_info.sleep_ivol_arg;
+       char *cmd, *sleep_stream = args_info.sleep_stream_given?
+               args_info.sleep_stream_arg : NULL;
+
+       if (sf) {
+               PARA_INFO_LOG("initial volume: %d\n", iv);
+               set_vol(iv);
+               cmd = make_message("csp %s\n", fa_stream);
+               client_cmd(cmd);
+               free(cmd);
+               fade(sv, sf);
+       }
+       if (sleep_stream) {
+               cmd = make_message("csp %s\n", sleep_stream);
+               client_cmd(cmd);
+               free(cmd);
+       } else
+               client_cmd("stop");
+       if (!wf)
+               return;
+       /* calculate wake time */
+       time(&t1);
+       tm = localtime(&t1);
+       if (tm->tm_hour > hour || (tm->tm_hour == hour && tm->tm_min> min)) {
+               /* wake time is tomorrow */
+               t1 += 86400;
+               tm = localtime(&t1);
+               t1 -= 86400;
+       }
+       tm->tm_hour = hour;
+       tm->tm_min = min;
+       tm->tm_sec = 0;
+       wake_time_epoch = mktime(tm);
+       PARA_INFO_LOG("waketime: %s", asctime(tm));
+       while (wake_time_epoch > t1 + wf) {
+               delay = wake_time_epoch - t1 - wf;
+               PARA_INFO_LOG("sleeping %u seconds (%u:%02u)\n",
+                       delay, delay / 3600, 
+                       (delay % 3600) / 60);
+               sleep(delay);
+               time(&t1);
+       }
+       cmd = make_message("csp %s\n", wake_stream);
+       client_cmd(cmd);
+       free(cmd);
+       fade(wv, wf);
+       PARA_INFO_LOG("%s", "fade complete, returning\n");
+}
+
+static void snooze(void)
+{
+       if (get_vol() < args_info.snooze_out_vol_arg)
+               set_vol(args_info.snooze_out_vol_arg);
+       else
+               fade(args_info.snooze_out_vol_arg, args_info.snooze_out_fade_arg);
+       client_cmd("pause");
+       PARA_NOTICE_LOG("%d seconds snooze time...\n", args_info.snooze_time_arg);
+       sleep(args_info.snooze_time_arg);
+       client_cmd("play");
+       fade(args_info.snooze_in_vol_arg, args_info.snooze_in_fade_arg);
+}
+
+static int configfile_exists(void)
+{
+        static char *config_file;
+
+        if (!args_info.config_file_given) {
+               char *home = para_homedir();
+               const char *conf = ".paraslash/fade.conf";
+               free(config_file);
+               config_file = make_message("%s/%s", home, conf);
+               free(home);
+                args_info.config_file_arg = config_file;
+        }
+        return file_exists(args_info.config_file_arg);
+}
+
+
+int main(int argc, char *argv[])
+{
+       int ret;
+
+       if (cmdline_parser(argc, argv, &args_info))
+               exit(EXIT_FAILURE);
+       ret = configfile_exists();
+       if (!ret && args_info.config_file_given) {
+               PARA_EMERG_LOG("can not read config file %s\n",
+                       args_info.config_file_arg);
+               exit(EXIT_FAILURE);
+       }
+       if (ret)
+               cmdline_parser_configfile(args_info.config_file_arg,
+                       &args_info, 0, 0, 0);
+       if ((ret = open_mixer()) < 0) {
+               PARA_EMERG_LOG("can not open mixer device %s.",
+                       args_info.mixer_device_arg);
+               exit(EXIT_FAILURE);
+       } else
+               close(ret);
+       ret = 0;
+       setlinebuf(stdout);
+       if (!strcmp(args_info.mode_arg, "sleep")) {
+               sweet_dreams();
+               goto out;
+       }
+       if (!strcmp(args_info.mode_arg, "fade")) {
+               fade(args_info.fade_vol_arg, args_info.fade_time_arg);
+               goto out;
+       }
+       if (!strcmp(args_info.mode_arg, "snooze")) {
+               snooze();
+               goto out;
+       }
+       ret = -1;
+out:
+       return ret;
+}
diff --git a/fade.ggo b/fade.ggo
new file mode 100644 (file)
index 0000000..c57267d
--- /dev/null
+++ b/fade.ggo
@@ -0,0 +1,27 @@
+section "general options"
+option "mode" o "{sleep|fade|snooze}" string default="sleep" no
+option "config_file" c "(default='~/.paraslash/fade.conf')" string typestr="filename" no
+option "mixer_device" m "mixer device file" string typestr="device" default="/dev/mixer" no
+
+section "sleep options (only relevant in sleep mode)"
+option "sleep_ivol" - "set initial volume before doing anything else" int typestr="volume" default="60" no
+option "fa_stream" - "fall asleep stream. Change to this stream right after setting the volume" string typestr="streamname" default="fa" no
+option "fa_fade" - "fall asleep fading time, no fading if set to 0" int typestr="seconds" default="1800" no
+option "fa_vol" - "volume to fade to" int typestr="volume" default="20" no
+option "sleep_stream" - "change to this stream after fading, stop playing if unset" string typestr="streamname" default="sleep" no
+option "wake_hour" H "(0-23)" int default="8" no
+option "wake_min" M "(0-59)" int default="0" no
+option "wake_stream" - "changed to on waketime" string typestr="streamname" default="wake" no
+option "wake_fade" - "no fading in if set to 0" int typestr="seconds" default="1200" no
+option "wake_vol" - "vol to fade to at waketime" int typestr="volume" default="80" no
+
+section "snooze options"
+option "snooze_out_fade" - "fade out time" int typestr="seconds" default="30" no
+option "snooze_out_vol" - "vol to fade to before snooze" int typestr="volume" default="20" no
+option "snooze_time" - "delay" int typestr="seconds" default="600" no 
+option "snooze_in_fade" - "fade in time" int typestr="seconds" default="180" no
+option "snooze_in_vol" - "vol to fade to after snooze" int typestr="volume" default="80" no
+
+section "fade options"
+option "fade_vol" f "volume to fade to" int typestr="volume" default="50" no
+option "fade_time" t "time to fade in" int typestr="seconds" default="5" no
diff --git a/filter.c b/filter.c
new file mode 100644 (file)
index 0000000..2e5e205
--- /dev/null
+++ b/filter.c
@@ -0,0 +1,175 @@
+/*
+ * 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 filter.c the stand-alone filter program */
+
+#include "gcc-compat.h"
+#include "para.h"
+
+#include "filter.cmdline.h"
+#include "list.h"
+#include "filter.h"
+#include "error.h"
+#include "string.h"
+
+INIT_FILTER_ERRLISTS;
+
+#define INBUF_SIZE 32 * 1024
+
+static struct filter_chain_info filter_chain_info_struct;
+static struct filter_chain_info *fci = &filter_chain_info_struct;
+
+struct gengetopt_args_info conf;
+
+__printf_2_3 void para_log(int ll, char* fmt,...)
+{
+       va_list argp;
+
+       /* ignore log message if loglevel is not high enough */
+       if (ll < conf.loglevel_arg)
+               return;
+       va_start(argp, fmt);
+       vfprintf(stderr, fmt, argp);
+       va_end(argp);
+}
+
+static char *inbuf;
+static size_t loaded;
+static int eof;
+
+static int init_active_filter_list(void)
+{
+       int i, filter_num;
+       struct filter_node *fn;
+
+       INIT_LIST_HEAD(&fci->filters);
+
+       fci->inbuf = inbuf;
+       fci->in_loaded = &loaded;
+       fci->eof = &eof;
+
+       for (i = 0; i < conf.filter_given; i++) {
+               char *fa = para_strdup(conf.filter_arg[i]);
+               fn = para_calloc(sizeof(struct filter_node));
+               filter_num = check_filter_arg(fa, &fn->conf);
+               if (filter_num < 0) {
+                       free(fn);
+                       return filter_num;
+               }
+               fn->fci = fci;
+               INIT_LIST_HEAD(&fn->callbacks);
+               fn->filter = &filters[filter_num];
+               PARA_DEBUG_LOG("adding %s to filter chain\n", fn->filter->name);
+               list_add_tail(&fn->node, &fci->filters);
+       }
+       if (list_empty(&fci->filters))
+               return -E_NO_FILTERS;
+       return 1;
+}
+
+static void open_filters(void)
+{
+       struct filter_node *fn;
+
+       list_for_each_entry(fn, &fci->filters, node) {
+               fn->filter->open(fn);
+               PARA_INFO_LOG("opened %s filter\n", fn->filter->name);
+               fci->outbuf = fn->buf;
+               fci->out_loaded = &fn->loaded;
+       }
+}
+
+static int parse_config(int argc, char *argv[])
+{
+       static char *cf; /* config file */
+        struct stat statbuf;
+       int i;
+
+       if (cmdline_parser(argc, argv, &conf))
+               return -E_FILTER_SYNTAX;
+       if (!cf) {
+               char *home = para_homedir();
+               cf = make_message("%s/.paraslash/filter.conf", home);
+               free(home);
+       }
+        if (!stat(cf, &statbuf)) {
+               if (cmdline_parser_configfile(cf, &conf, 0, 0, 0))
+                       return -E_FILTER_SYNTAX;
+       }
+       if (!conf.list_filters_given)
+               return 1;
+       printf("available filters: ");
+       for (i = 0; filters[i].name; i++)
+               printf("%s%s", i? " " : "", filters[i].name);
+       printf("\nTry para_filter -f<filtername>:-h for help on <filtername>\n");
+       exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char *argv[])
+{
+       int converted, ret;
+       char *ib, *ob; /* input/output buffer */
+       size_t *il, *ol; /* number of loaded bytes in input/output buffer */
+
+       filter_init(filters);
+       ret = parse_config(argc, argv);
+       if (ret < 0)
+               goto out;
+       inbuf = para_malloc(INBUF_SIZE);
+       ret = init_active_filter_list();
+       if (ret < 0)
+               goto out;
+       open_filters();
+       ib = fci->inbuf;
+       ob = fci->outbuf;
+       il = fci->in_loaded;
+       ol = fci->out_loaded;
+       PARA_DEBUG_LOG("ib %p in, ob: %p\n", ib, ob);
+again:
+       if (*il < INBUF_SIZE && !eof) {
+               ret  = read(STDIN_FILENO, ib + *il, INBUF_SIZE - *il);
+               PARA_DEBUG_LOG("read %d/%d\n", ret, INBUF_SIZE - *il);
+               if (ret < 0)
+                       goto out;
+               if (!ret)
+                       eof = 1;
+               *il += ret;
+       }
+       ret = filter_io(fci);
+       if (ret < 0)
+               goto out;
+       converted = ret;
+       if (*ol) {
+               ret = write(STDOUT_FILENO, ob, *ol);
+               PARA_DEBUG_LOG("wrote %d/%d\n", ret, *ol);
+               if (ret <= 0)
+                       goto out;
+               *ol -= ret;
+               if (*ol) {
+                       PARA_NOTICE_LOG("short write: %d bytes left\n", *ol);
+                       memmove(ob, ob + ret, *ol);
+               }
+       }
+       if (!eof || converted)
+               goto again;
+       ret = 0;
+out:
+       if (ret < 0)
+               PARA_EMERG_LOG("%s\n", PARA_STRERROR(-ret));
+       close_filters(fci);
+       return ret;
+}
diff --git a/filter.ggo b/filter.ggo
new file mode 100644 (file)
index 0000000..4157835
--- /dev/null
@@ -0,0 +1,18 @@
+option "loglevel" l "set loglevel (0-6)" int typestr="level" default="4" no
+option "filter" f "Specify filter.
+
+May be given multiple times to 'pipe' the stream
+through arbitrary many filters in an efficient
+way.  The same filter may appear more than once,
+order matters.
+
+Filter options may be specified for each '-f'
+option separately. Insinde these options ':'
+must be used as the separator instead of white
+space. Example:
+
+       -f compress:--anticlip:--volume:2
+"
+string typestr="filter_spec" no multiple
+
+option "list_filters" L "print list of available filters and exit" flag off
diff --git a/filter.h b/filter.h
new file mode 100644 (file)
index 0000000..70f4f4f
--- /dev/null
+++ b/filter.h
@@ -0,0 +1,349 @@
+/*
+ * 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 filter.h filter-related structures and exported symbols from filter_chain.c */
+
+/**
+ * describes one running instance of a chain of filters
+ *
+ */
+struct filter_chain_info {
+/**
+ *
+ *
+ * the number of channels of the current stream
+ *
+ * Set by the decoding filter
+ */
+       unsigned int channels;
+/**
+ *
+ *
+ * current samplerate in Hz
+ *
+ * Set by the decoding filter
+ */
+       unsigned int samplerate;
+/**
+ *
+ *
+ * the list containing all filter nodes in this filter chain
+ */
+       struct list_head filters;
+/**
+ *
+ *
+ * the input buffer of the filter chain
+ *
+ * This is set to point to the output buffer of the receiving application (the
+ * buffer used to read from stdin for para_filter; the output buffer of the
+ * current receiver for para_audiod)
+ */
+       char *inbuf;
+/**
+ *
+ *
+ * the output buffer of the filter chain
+ *
+ * Points to the output buffer of the last filter in the filter chain
+**/
+       char *outbuf;
+/**
+ *
+ *
+ * pointer to variable containing the number of bytes loaded in the input buffer
+ */
+       size_t *in_loaded;
+/**
+ *
+ *
+ * pointer to variable containing the number of bytes loaded in the output buffer
+ */
+       size_t *out_loaded;
+/**
+ *
+ *
+ * non-zero if end of file was encountered
+ */
+       int *eof;
+/**
+ *
+ *
+ * non-zero if an error occured
+ */
+       int error;
+};
+
+/**
+ * describes one running instance of a filter
+*/
+struct filter_node {
+/**
+ *
+ *
+ * a pointer to the corresponding filter struct
+ */
+       struct filter *filter;
+/**
+ *
+ *
+ * the filter chain this filter node belongs to
+ */
+       struct filter_chain_info *fci;
+/**
+ *
+ *
+ * the position of the filter in the corresponding filter chain
+ *
+ * all filters that make up the filter chains are organized in a doubly
+ * linked list.
+ */
+       struct list_head node;
+/**
+ *
+ *
+ * each filter may store any filter-specific information about the particular
+ * instance of the filter here.
+ */
+       void *private_data;
+/**
+ *
+ *
+ * the output buffer
+ */
+       char *buf;
+/**
+ * the size of the output buffer
+ */
+       size_t bufsize;
+/**
+ *
+ *
+ * the number of bytes currently loaded in \a buf
+ */
+       size_t loaded;
+/**
+ *
+ *
+ * the list of registered callbacks
+ */
+       struct list_head callbacks;
+/**
+ *
+ * a pointer to the configuration of this instance
+ */
+       void *conf;
+};
+
+/**
+ * used to manage grab clients
+ *
+ * An application using paraslash's filter subsystem may register any number of
+ * callbacks for each filter_node. It is possible to attach a filter callback
+ * while the filter is running. This is used for stream grabbing in
+ * para_audiod: Whenever a client sends the 'grab' command, para_audiod adds a
+ * filter callback to the list of callbacks for the filter node specified in
+ * the grab command.
+ */
+struct filter_callback {
+/**
+ *
+ *
+ * all callbacks are organized in a doubly linked list
+ */
+       struct list_head node;
+/**
+ *
+ *
+ * private data
+ *
+ * May be initialized by the application before registering the callback. This
+ * pointer is not used by the filter subsystem. It is provided for use within
+ * the input/ouput/close callback functions.
+ */
+       void *data;
+/**
+ *
+ *
+ * the input callback
+ *
+ * In not \p NULL, the filter subsystem calls this function whenever the filter
+ * consumed some or all of its input buffer. A pointer to the buffer of consumed
+ * data, its length and a pointer to the own \a filter_callback structure are passed
+ * to \a input_cb. The input callback is expected to return a negative value on errors.
+ */
+       int (*input_cb)(char *buf, size_t len, struct filter_callback *fc);
+/**
+ *
+ *
+ * the output callback
+ *
+ * If not NULL, this is called whenever the filter produces output. A pointer
+ * to the output data, its length and a pointer to the own \a filter_callback
+ * structure are passed to \a output_cb. Like the input callback, the output
+ * callback is expected to return a negative value on errors.
+ */
+       int (*output_cb)(char *buf, size_t len, struct filter_callback *fc);
+/**
+ *
+ *
+ * the callback close function
+ *
+ * This gets called whenever the input/ouput callback returned an error, or if
+ * the filter chain is going to be destroyed, e.g. because the end of the
+ * stream was encounterd. It is assumed to succeed.
+ */
+       void (*close)(struct filter_callback *fc);
+};
+
+
+void close_filters(struct filter_chain_info *fci);
+int filter_io(struct filter_chain_info *fci);
+void filter_init(struct filter *all_filters);
+int check_filter_arg(char *filter_arg, void **conf);
+int del_filter_callback(struct filter_callback *fcb);
+
+/**
+ * the structure associated with a paraslash filter
+ *
+ * Paraslash filters are "modules" which are used to transform an audio stream.
+ * struct filter contains pointers to functions that must be supplied by the
+ * filter code in order to be used by the driving application (currently
+ * para_audiod and para_filter).
+ *
+ * Note: As several instances of the same filter may be running at the same
+ * time, all these filter functions must be reentrant; no static non-constant
+ * variables may be used.
+ * \sa mp3dec.c, oggdec.c, wav.c, compress.c, filter_node
+ */
+struct filter {
+/**
+ *
+ *
+ * the name of the filter
+ */
+const char *name;
+/**
+ *
+ *
+ * pointer to the filter init routine
+ *
+ * This function is only called once at startup. It must initialize the
+ * other non-optional function pointers of \a f.
+ */
+void (*init)(struct filter *f);
+/**
+ *
+ *
+ * open one instance of this filter
+ *
+ * This should allocate the output buffer of the given filter node and do any
+ * other filter-specific preparations like initializing the private_data member
+ * of \a fn suitably. The open function is assumed to succeed.
+ */
+void (*open)(struct filter_node *fn);
+/**
+ *
+ *
+ * convert (filter) the given data
+ *
+ * Pointer to the converting function of the filter. It should convert the
+ * given input buffer \a inbuf which is of length \a len to the previoulsy
+ * reserved output buffer of \a fn. On success, it must return the number of
+ * bytes it consumed from \a inbuf. On errors, a negative number indicating the
+ * kind of the error must be returned.
+ *
+ * A zero return value just means that nothing was converted (probably because
+ * the input buffer was too small). This is not interpreted as an error.
+ */
+ssize_t (*convert)(char *inbuf, size_t len, struct filter_node *fn);
+/**
+ *
+ *
+ * close one instance of this filter
+ *
+ * Free all resources of associated with \a fn that were previously allocated
+ * by the open() function.
+ */
+void (*close)(struct filter_node *fn);
+/**
+ *
+ *
+ * print the help text for this filter and exit
+ *
+ * This is optional and it is not necessary to initialize this pointer if
+ * the filter does not have a help text.
+ */
+void (*print_help)(void);
+/**
+ *
+ *
+ * a pointer to the filter's command line parser
+ *
+ * If this optional function pointer is not NULL, any filter options are passed
+ * from the main propgram to this command line parser once at application
+ * startup. The command line parser should check its command line options given
+ * by \a argc and \a argv and abort on errors. On success, it should return a
+ * pointer to the filter-specific configuration data determined by \a argc and
+ * \a argv.
+ */
+void *(*parse_config)(int argc, char **argv);
+};
+/** \cond */
+extern struct filter filters[];
+#define DECLARE_EXTERN_FILTER_INIT(name) \
+       extern void name ## _init(struct filter *f)
+
+#define FILTER_INIT(filter) { \
+       .name = #filter, \
+       .init = filter ## _init, \
+       .parse_config = NULL, \
+       .print_help = NULL \
+},
+
+/* filters that are always present */
+DECLARE_EXTERN_FILTER_INIT(wav);
+/* wav is always the first filter */
+#define WAV_FILTER_NUM 0
+DECLARE_EXTERN_FILTER_INIT(compress);
+
+/* next the optional filters */
+#ifdef HAVE_MAD
+DECLARE_EXTERN_FILTER_INIT(mp3dec);
+#define MP3DEC_FILTER FILTER_INIT(mp3dec)
+#else
+#define MP3DEC_FILTER
+#endif
+
+#ifdef HAVE_OGGVORBIS
+DECLARE_EXTERN_FILTER_INIT(oggdec);
+#define OGGDEC_FILTER FILTER_INIT(oggdec)
+#else
+#define OGGDEC_FILTER
+#endif
+
+/*
+ * a macro that defines an array of all available filters
+ */
+#define DEFINE_FILTER_ARRAY(fa) struct filter fa[] = { \
+       FILTER_INIT(wav) \
+       FILTER_INIT(compress) \
+       MP3DEC_FILTER \
+       OGGDEC_FILTER \
+       { .name = NULL } };
+/** \endcond */
diff --git a/filter_chain.c b/filter_chain.c
new file mode 100644 (file)
index 0000000..d22ef28
--- /dev/null
@@ -0,0 +1,253 @@
+/*
+ * 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 filter_chain.c common helper functions for filter input/output */
+
+#include "gcc-compat.h"
+#include "para.h"
+#include "list.h"
+#include "filter.h"
+#include "error.h"
+#include "string.h"
+
+DEFINE_FILTER_ARRAY(filters);
+
+/**
+ * call the init function of each supported filter
+ *
+ * \param all_filters the array of all supported filters
+ * \sa filter::init
+ */
+void filter_init(struct filter *all_filters)
+{
+       struct filter *f;
+
+       for (f = all_filters; f->name; f++)
+               f->init(f);
+}
+
+/**
+ * close and destroy a filter callback
+ *
+ * \param fcb the filter callback to close
+ *
+ * This removes \a fcb from the list of filter callbacks and calls
+ * the close callback associated with \a fcb.
+ */
+static void close_filter_callback(struct filter_callback *fcb)
+{
+       PARA_NOTICE_LOG("closing filter_callback %p, data: %p\n", fcb, fcb->data);
+       list_del(&fcb->node);
+       fcb->close(fcb);
+}
+
+/**
+ * close all callbacks of a filter node
+ *
+ * \param fn the filter node which contains the filter callbacks to be closed
+ *
+ * Call close_filter_callback() for each entry in the filter callback list
+ * of \a fn.
+ */
+static void close_callbacks(struct filter_node *fn)
+{
+       struct filter_callback *fcb, *tmp;
+
+       list_for_each_entry_safe(fcb, tmp, &fn->callbacks, node)
+               close_filter_callback(fcb);
+}
+
+static void call_callbacks(struct filter_node *fn, char *inbuf, size_t inlen,
+       char *outbuf, size_t outlen)
+{
+       struct filter_callback *fcb, *tmp;
+       list_for_each_entry_safe(fcb, tmp, &fn->callbacks, node) {
+               int ret;
+               if (inlen && fcb->input_cb) {
+                       ret = fcb->input_cb(inbuf, inlen, fcb);
+                       if (ret < 0) {
+                               close_filter_callback(fcb);
+                               continue;
+                       }
+               }
+               if (!outlen || !fcb->output_cb)
+                       continue;
+               ret = fcb->output_cb(outbuf, outlen, fcb);
+               if (ret < 0)
+                       close_filter_callback(fcb);
+       }
+}
+
+/**
+ * call the convert function of each filter
+ *
+ * \param fci the filter chain containing the list of filter nodes.
+ *
+ * This is the core function of the filter subsystem. It loops over the list of
+ * filter nodes determined by \a fci and calls the filter's convert function if
+ * there is input available for the filter node in question. If the convert
+ * function consumed some or all of its input data, all registered input
+ * callbacks are called.  Similarly, if a convert function produced output, all
+ * registerd output callbacks get called.
+ *
+ * \return The sum of output bytes produced by the convert functions on success,
+ * negative return value on errors.
+ *
+ * \sa filter_node, filter#convert, filter_callback
+ */
+int filter_io(struct filter_chain_info *fci)
+{
+       struct filter_node *fn;
+       char *ib;
+       size_t *loaded;
+       int conv, conv_total = 0;
+again:
+       ib = fci->inbuf;
+       loaded = fci->in_loaded;
+       conv = 0;
+       list_for_each_entry(fn, &fci->filters, node) {
+               int ret;
+               if (*loaded && fn->loaded < fn->bufsize) {
+                       size_t old_fn_loaded = fn->loaded;
+                       PARA_DEBUG_LOG("fc %p loaded: %d, calling %s convert\n", fci, *loaded, fn->filter->name);
+                       ret = fn->filter->convert(ib, *loaded, fn);
+                       if (ret < 0) {
+                               if (!fci->error)
+                                       fci->error = -ret;
+                               return ret;
+                       }
+                       call_callbacks(fn, ib, ret, fn->buf + old_fn_loaded, fn->loaded - old_fn_loaded);
+                       *loaded -= ret;
+                       conv += ret;
+                       if (*loaded && ret) {
+                               PARA_DEBUG_LOG("moving %d bytes in input buffer for %s filter\n",
+                                       *loaded,  fn->filter->name);
+                               memmove(ib, ib + ret, *loaded);
+                       }
+               }
+               ib = fn->buf;
+               loaded = &fn->loaded;
+       }
+//     PARA_DEBUG_LOG("loaded: %d\n", *loaded);
+       conv_total += conv;
+       if (conv)
+               goto again;
+       return conv_total;
+}
+
+/**
+ * close all filter nodes and its callbacks
+ *
+ * \param fci the filter chain to close
+ *
+ * For each filter node determined by \a fci, call the close function of each
+ * registered filter callback as well as the close function of the
+ * corresponding filter.  Free all resources and destroy all callback lists and
+ * the filter node list.
+ *
+ * \sa filter::close, filter_callback::close
+ */
+void close_filters(struct filter_chain_info *fci)
+{
+       struct filter_node *fn, *tmp;
+
+       if (!fci)
+               return;
+       PARA_DEBUG_LOG("closing filter chain %p\n", fci);
+       list_for_each_entry_safe(fn, tmp, &fci->filters, node) {
+               PARA_NOTICE_LOG("closing %s filter callbacks (fci %p, fn %p)\n", fn->filter->name, fci, fn);
+               close_callbacks(fn);
+               PARA_NOTICE_LOG("closing %s filter (fci %p, fn %p)\n", fn->filter->name, fci, fn);
+               fn->filter->close(fn);
+               list_del(&fn->node);
+               free(fn);
+       }
+}
+
+/*
+ * If the filter has a command line parser and options is not NULL, run it.
+ * Returns filter_num on success, negative on errors
+ */
+static int parse_filter_args(int filter_num, char *options, void **conf)
+{
+       struct filter *f = &filters[filter_num];
+       int i, argc = 2;
+       char *dummy_args[] = {"", "", NULL};
+       char **argv;
+
+//     PARA_DEBUG_LOG("%s, options: %s, parser: %p\n", f->name,
+//             options? options : "(none)", f->parse_config);
+       if (!f->parse_config)
+               return options? -E_BAD_FILTER_OPTIONS : filter_num;
+       if (options) {
+//             PARA_DEBUG_LOG("options: %s\n", options);
+               argc = split_args(options, &argv, ':');
+//             PARA_DEBUG_LOG("argc = %d, argv[0]: %s\n", argc, argv[0]);
+               for (i = argc; i >= 0; i--)
+                       argv[i + 1] = argv[i];
+               argc += 2;
+               *conf = f->parse_config(argc, argv);
+       } else {
+               /* is it OK to have no options? */
+               *conf = f->parse_config(2, dummy_args);
+       }
+       return *conf? filter_num : -E_BAD_FILTER_OPTIONS;
+}
+
+/**
+ * check the filter command line options
+ *
+ * \param fa the command line options (values separated by colons)
+ * \param conf points to the filter configuration upon successful return
+ *
+ * Check if \a fa starts with a the name of a supported filter, followed by
+ * a colon. If yes, call the command line parser of that filter.
+ *
+ * \return On success, the number of the filter is returned and \a conf
+ * is initialized to point to the filter configuration determined by \a fa.
+ * On errors, a negative value is returned.
+ *
+ * Note: If \a fa specifies a filter that has no command line parser success is
+ * returned, and \a conf is initialized to \p NULL.
+ *
+ * \sa filter::parse_config
+ */
+int check_filter_arg(char *fa, void **conf)
+{
+       int j;
+
+       *conf = NULL;
+//     PARA_DEBUG_LOG("arg: %s\n", fa);
+       for (j = 0; filters[j].name; j++) {
+               const char *name = filters[j].name;
+               size_t len = strlen(name);
+               char c;
+               if (strlen(fa) < len)
+                       continue;
+               if (strncmp(name, fa, len))
+                       continue;
+               c = fa[len];
+               if (c && c != ':')
+                       continue;
+               if (c && !filters[j].parse_config)
+                       return -E_BAD_FILTER_OPTIONS;
+               return parse_filter_args(j, c? fa + len + 1 : NULL, conf);
+       }
+       return -E_UNSUPPORTED_FILTER;
+}
+
diff --git a/fonts/24P_Arial_Blue.png b/fonts/24P_Arial_Blue.png
new file mode 100644 (file)
index 0000000..bc71187
Binary files /dev/null and b/fonts/24P_Arial_Blue.png differ
diff --git a/fonts/24P_Arial_Metallic_Yellow.png b/fonts/24P_Arial_Metallic_Yellow.png
new file mode 100644 (file)
index 0000000..2eec399
Binary files /dev/null and b/fonts/24P_Arial_Metallic_Yellow.png differ
diff --git a/fonts/24P_Arial_NeonBlue.png b/fonts/24P_Arial_NeonBlue.png
new file mode 100644 (file)
index 0000000..184218f
Binary files /dev/null and b/fonts/24P_Arial_NeonBlue.png differ
diff --git a/fonts/24P_Arial_NeonYellow.png b/fonts/24P_Arial_NeonYellow.png
new file mode 100644 (file)
index 0000000..eecd4e5
Binary files /dev/null and b/fonts/24P_Arial_NeonYellow.png differ
diff --git a/fonts/24P_Copperplate_Blue.png b/fonts/24P_Copperplate_Blue.png
new file mode 100644 (file)
index 0000000..7d48195
Binary files /dev/null and b/fonts/24P_Copperplate_Blue.png differ
diff --git a/gcc-compat.h b/gcc-compat.h
new file mode 100644 (file)
index 0000000..a3b20d3
--- /dev/null
@@ -0,0 +1,43 @@
+#if __GNUC__ >= 3
+# define inline                inline __attribute__ ((always_inline))
+# define __pure                __attribute__ ((pure))
+# define __noreturn    __attribute__ ((noreturn))
+# define __malloc      __attribute__ ((malloc))
+# define __used                __attribute__ ((used))
+# define __unused      __attribute__ ((unused))
+# define __packed      __attribute__ ((packed))
+# define likely(x)     __builtin_expect (!!(x), 1)
+# define unlikely(x)   __builtin_expect (!!(x), 0)
+/* 
+ * p is the number of the "format string" parameter, and q is 
+ * the number of the first variadic parameter 
+ */
+# define __printf(p,q) __attribute__ ((format (printf, p, q)))
+/*
+ * as direct use of __printf(p,q) confuses doxygen, here are two extra macros
+ * for those values p,q that are actually used by paraslash.
+ */
+#define  __printf_1_2 __printf(1,2)
+#define  __printf_2_3 __printf(2,3)
+
+#else
+
+# define inline                /* no inline */
+# define __pure                /* no pure */
+# define __noreturn    /* no noreturn */
+# define __malloc      /* no malloc */
+# define __used                /* no used */
+# define __unused      /* no unused */
+# define __packed      /* no packed */
+# define likely(x)     (x)
+# define unlikely(x)   (x)
+# define __printf(p,q) /* no format */
+#define  __printf_1_2
+#define  __printf_2_3
+#endif
+
+# if __GNUC__ >=3 && __GNUC_MINOR__ > 3
+# define __must_check  __attribute__ ((warn_unused_result))
+# else
+# define __must_check  /* no warn_unused_result */
+# endif
diff --git a/grab_client.c b/grab_client.c
new file mode 100644 (file)
index 0000000..cd6cffb
--- /dev/null
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2006 Andre Noll <maan@systemlinux.org>
+ *
+ *     This program is free software; you can redistribute it and/or modify
+ *     it under the terms of the GNU General Public License as published by
+ *     the Free Software Foundation; either version 2 of the License, or
+ *     (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU General Public License for more details.
+ *
+ *     You should have received a copy of the GNU General Public License
+ *     along with this program; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ */
+
+/**
+ * \file grab_client.c functions for grabbing the stream at any position
+ * in a filter chain
+ *
+ * \sa filter_chain filter_chain_info filter
+ */
+
+#include "gcc-compat.h"
+#include "para.h"
+#include "close_on_fork.h"
+#include "grab_client.cmdline.h"
+#include "list.h"
+#include "filter.h"
+#include "grab_client.h"
+#include "audiod.h"
+#include "error.h"
+#include "string.h"
+
+
+/** this maps the enum to the text used at the command line */
+static const char *gc_modes[] = {
+       [GRAB_PEDANTIC] = "pedantic",
+       [GRAB_SLOPPY] = "sloppy",
+       [GRAB_AGGRESSIVE] = "aggressive",
+       NULL
+};
+
+/** grab clients that are not yet attached to a filter node */
+struct list_head inactive_grab_client_list;
+
+static int max_num_filters(void)
+{
+       int i, ret = 0;
+       for (i = 0; audio_formats[i]; i++) {
+               PARA_INFO_LOG("%s filter chain length: %d\n", audio_formats[i],
+                       num_filters(i));
+               ret = MAX(ret, num_filters(i));
+       }
+       PARA_INFO_LOG("maximal filter chain length: %d\n", ret);
+       return ret;
+}
+
+static int gc_write(char *buf, size_t len, struct filter_callback *fcb)
+{
+       struct grab_client *gc = fcb->data;
+       struct timeval tv = {0, 100};
+       int ret;
+
+//     PARA_INFO_LOG("writing %d bytes to fd %d\n", len, gc->fd);
+       fd_set wfds;
+       do {
+               FD_ZERO(&wfds);
+               FD_SET(gc->fd, &wfds);
+               ret = select(gc->fd + 1, NULL, &wfds, NULL, &tv);
+       } while (ret == EAGAIN || ret == EINTR);
+       if (ret != 1) {
+               if (gc->mode == GRAB_PEDANTIC)
+                       return -E_PEDANTIC_GRAB;
+               if (gc->mode == GRAB_SLOPPY)
+                       return 1;
+       }
+rewrite:
+       ret = write(gc->fd, buf, len);
+       if (ret < 0) {
+               ret = -E_GC_WRITE;
+               gc->error = E_GC_WRITE;
+       } else {
+               if (ret != len) {
+                       if (gc->mode == GRAB_PEDANTIC)
+                               return -E_PEDANTIC_GRAB;
+                       if (gc->mode == GRAB_AGGRESSIVE) {
+                               len -= ret;
+                               memmove(buf, buf + ret, len);
+                               goto rewrite;
+                       }
+               }
+       }
+       return ret;
+}
+
+static int check_gc_args(struct grab_client *gc)
+{
+       int i;
+       struct grab_client_args_info *conf = gc->conf;
+
+       PARA_INFO_LOG("filter_num: %d\n", gc->conf->filter_num_arg);
+       for (i = 0; gc_modes[i]; i++)
+               if (!strcmp(conf->mode_arg, gc_modes[i]))
+                       break;
+       if (!gc_modes[i])
+               return -E_INVALID_GRAB_MODE;
+       gc->mode = i;
+       if (conf->audio_format_given) {
+               gc->audio_format_num = get_audio_format_num(conf->audio_format_arg);
+               if (gc->audio_format_num < 0)
+                       return gc->audio_format_num;
+       }
+       if (conf->slot_arg > MAX_STREAM_SLOTS)
+               return -E_BAD_GC_SLOT;
+       if (conf->filter_num_arg <= 0)
+               return -E_BAD_GC_FILTER_NUM;
+       if (conf->audio_format_given) {
+               if (num_filters(gc->audio_format_num) < conf->filter_num_arg)
+                       return -E_BAD_GC_FILTER_NUM;
+       } else
+               if (conf->filter_num_arg > max_num_filters())
+                       return -E_BAD_GC_FILTER_NUM;
+
+       return 1;
+}
+
+static void add_inactive_gc(struct grab_client *gc)
+{
+       PARA_INFO_LOG("adding grab client %p (fd %d) to inactive list\n",
+               gc, gc->fd);
+       list_add(&gc->node, &inactive_grab_client_list);
+}
+
+static void gc_free(struct grab_client *gc)
+{
+       int i;
+
+       for (i = 0; i < gc->argc; i++)
+               free(gc->argv[i]);
+       free(gc->argv);
+       free(gc->conf);
+       free(gc);
+
+}
+
+static void gc_close(struct filter_callback *fcb)
+{
+       struct grab_client *gc = fcb->data;
+
+       if (gc->conf->one_shot_given || gc->error) {
+               PARA_INFO_LOG("closing fd %d (grab client %p)\n", gc->fd, gc);
+               del_close_on_fork_list(gc->fd);
+               close(gc->fd);
+               gc_free(gc);
+               /* close on fork ?*/
+               return;
+       }
+       add_inactive_gc(gc);
+}
+
+/**
+ * move a grab client from the inactive list to a filter node
+ *
+ * \param gc the grab client to activate
+ * \param fn the filter node \a gc gets attached to
+ *
+ * \sa filter_node::callbacks, inactive_grab_client_list
+ */
+void activate_grab_client(struct grab_client *gc, struct filter_node *fn)
+{
+       PARA_INFO_LOG("activating %p (fd %d, filter node: %p)\n", gc, gc->fd, fn);
+       list_del(&gc->node);
+       list_add(&gc->fcb.node, &fn->callbacks);
+}
+
+/**
+ * activate inactive grab clients if possible
+ *
+ * \param slot audiod's slot for the new audio file
+ * \param audio_format_num the number of the audio format of the new audio file
+ * \param filter_list the list of activated filters for that new audio file
+ *
+ * This is called from audiod.c when the current audio file changes. It loops
+ * over all inactive grab clients and checks each grab client's configuration
+ * to determine if the client in question wishes to grab the new stream.  If
+ * yes, this grab client is moved from the inactive grab client list to an
+ * appropriate filter_node.
+ *
+ * \sa filter_chain_info::filters, inactive_grab_client_list,
+ * activate_grab_client
+ */
+void activate_inactive_grab_clients(int slot, int audio_format_num,
+               struct list_head *filter_list)
+{
+       struct grab_client *gc, *tmp;
+       int i;
+       struct filter_node *fn;
+
+       list_for_each_entry_safe(gc, tmp, &inactive_grab_client_list, node) {
+//             PARA_INFO_LOG("checking inactive grab client %p\n", gc);
+               if (gc->conf->slot_arg >= 0 && gc->conf->slot_arg != slot)
+                       continue;
+               if (gc->audio_format_num >= 0 && gc->audio_format_num !=
+                               audio_format_num)
+                       continue;
+               if (gc->conf->filter_num_arg >= 0 &&
+                               num_filters(gc->audio_format_num)
+                               < gc->conf->filter_num_arg)
+                       continue;
+               i = 1;
+               list_for_each_entry(fn, filter_list, node) {
+                       if (gc->conf->filter_num_arg <= 0
+                               || i == gc->conf->filter_num_arg)
+                               break;
+                       i++;
+               }
+               activate_grab_client(gc, fn);
+       }
+}
+
+/**
+ * check the command line options and allocate a grab_client structure
+ *
+ * \param fd the file descriptor of the client
+ * \param argc the number of command line options
+ * \param argv pointers to the command line options
+ * \param err non-zero if an error occured
+ *
+ * If the command line options  given by \a argc and \a argv are valid.
+ * allocate a struct grab_client and initialize it with this valid
+ * configuration. Moreover, add the new grab client to the inactive list.
+ *
+ * \return On success, this function returns a pointer to the newly created
+ * struct. On errors, it returns NULL and sets \a err appropriately.
+ *
+ * \sa grab_client, inactive_grab_client_list, activate_grab_client,
+ * filter_node::callbacks
+ */
+/*
+ * argc, argv get freed when com_grab() returns, so we have to make a
+ * copy.
+ */
+__malloc struct grab_client *grab_client_new(int fd, int argc, char **argv, int *err)
+{
+       int i, ret;
+       struct grab_client *gc = para_calloc(sizeof(struct grab_client));
+
+       gc->conf = para_calloc(sizeof(struct grab_client_args_info));
+       gc->argc = argc;
+       gc->argv = para_calloc((argc + 1) * sizeof(char *));
+
+       for (i = 0; i < argc; i++) {
+               gc->argv[i] = para_strdup(argv[i]);
+               PARA_INFO_LOG("argc: %d, argv[%d]: %s\n", argc, i, gc->argv[i]);
+       }
+       PARA_INFO_LOG("argv[%d]: %s\n", argc, gc->argv[argc]);
+       ret = grab_client_cmdline_parser(gc->argc, gc->argv , gc->conf);
+       *err = -E_GC_SYNTAX;
+       if (ret)
+               goto err_out;
+       *err = -E_GC_HELP_GIVEN;
+       if (gc->conf->help_given)
+               goto err_out;
+       *err = check_gc_args(gc);
+       if (*err < 0)
+               goto err_out;
+       if (gc->conf->input_grab_given) {
+               gc->fcb.input_cb = gc_write;
+               gc->fcb.output_cb = NULL;
+       } else {
+               gc->fcb.output_cb = gc_write;
+               gc->fcb.input_cb = NULL;
+       }
+       gc->fd = fd;
+       gc->fcb.close = gc_close;
+       gc->fcb.data = gc;
+       add_inactive_gc(gc);
+       return gc;
+err_out:
+       for (i = 0; i < argc; i++)
+               free(gc->argv[i]);
+       free(gc->argv);
+       free(gc->conf);
+       free(gc);
+       return NULL;
+}
+
+/** initialize the grabbing subsystem.
+ *
+ * This has to be called once during startup before any other function from
+ * grab_client.c may be used. It initializes \a inactive_grab_client_list.
+ */
+void init_grabbing()
+{
+       PARA_INFO_LOG("%s", "grab init\n");
+       INIT_LIST_HEAD(&inactive_grab_client_list);
+}
+
diff --git a/grab_client.ggo b/grab_client.ggo
new file mode 100644 (file)
index 0000000..3f87e00
--- /dev/null
@@ -0,0 +1,6 @@
+option "filter_num" f "point of filter chain to grab" int typestr="num" default="1" no
+option "slot" s "only grab this slot; grab any slot if negative" int typestr="num" default="-1" no
+option "mode" m "sloppy, pedantic, or aggressive" string typestr="grab_mode" default="sloppy" no
+option "audio_format" a "only grab this type of input stream; grab any if empty" string typestr="name" default="" no
+option "input_grab" i "grab the filter input instead of its output" flag off no
+option "one_shot" o "stop grabbing if audio file changes" flag off no
diff --git a/grab_client.h b/grab_client.h
new file mode 100644 (file)
index 0000000..e66e9b2
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2006 Andre Noll <maan@systemlinux.org>
+ *
+ *     This program is free software; you can redistribute it and/or modify
+ *     it under the terms of the GNU General Public License as published by
+ *     the Free Software Foundation; either version 2 of the License, or
+ *     (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU General Public License for more details.
+ *
+ *     You should have received a copy of the GNU General Public License
+ *     along with this program; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ */
+
+/** \file grab_client.h exported symbols from grab_client.c */
+
+#include "config.h"
+/**
+ * handle blocking writes for the grab client fds
+ *
+ * - pedantic: close fd if write would block
+ * - sloppy: ignore the data and do not write
+ * - aggressive: write anyway (default)
+ *
+ */
+enum grab_mode {GRAB_PEDANTIC, GRAB_SLOPPY, GRAB_AGGRESSIVE};
+
+/** describes one active grab client
+ *
+ * \sa filter_callback, filter_node::callbacks
+ */
+struct grab_client {
+/** the file descriptor to send the grabbed stream to */
+       int fd;
+/** the command line options for this grab client */
+       struct grab_client_args_info *conf;
+/** pedantic, sloppy, or aggressive, computed from command line */
+       enum grab_mode mode;
+/** non-zero if the write() to \a fd failed */
+       int error;
+/** the number of the desired audio format, computed from command line */
+       int audio_format_num;
+/** the callback data which gets attached to a suitable filter_node */
+       struct filter_callback fcb;
+/** all grab clients belong either to a filter node or to the inactive list */
+       struct list_head node;
+/** the number of command line options */
+       int argc;
+/** pointers to the command line options */
+       char **argv;
+};
+
+__malloc struct grab_client *grab_client_new(int fd, int argc, char **argv, int *err);
+void activate_inactive_grab_clients(int slot, int audio_format_num, struct list_head *filter_list);
+void activate_grab_client(struct grab_client *gc, struct filter_node *fn);
+void init_grabbing(void);
diff --git a/gui.c b/gui.c
new file mode 100644 (file)
index 0000000..351748e
--- /dev/null
+++ b/gui.c
@@ -0,0 +1,1364 @@
+/*
+ * Copyright (C) 1998-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 gui.c ncurses-based interface for paraslash */
+
+#include "gui.cmdline.h"
+#include "para.h"
+#include "gcc-compat.h"
+#include <curses.h>
+#include "ringbuffer.h"
+#include "string.h"
+
+extern const char *status_item_list[NUM_STAT_ITEMS];
+static char *stat_content[NUM_STAT_ITEMS];
+
+#define STANDARD_STATUS_BAR "para_gui " VERSION " (hit ? for help)"
+
+static int signal_pipe;
+
+static void finish(int sig);
+
+static struct win_data {
+       WINDOW *win;
+       NCURSES_SIZE_T begx;
+       NCURSES_SIZE_T begy;
+       NCURSES_SIZE_T cols;
+       NCURSES_SIZE_T lines;
+} top, bot, sb, in, sep;
+
+#define RINGBUFFER_SIZE 512
+struct rb_entry {
+       char *msg;
+       size_t len;
+       int color;
+};
+void *bot_win_rb;
+#define NUM_LINES(len) (1 + (len) / bot.cols)
+
+static unsigned scroll_position;
+
+static int external_cmd_died, curses_active;
+static pid_t external_cmd_pid;
+
+static int command_pipe = -1;
+static int audiod_pipe = -1;
+static struct gengetopt_args_info conf;
+
+enum {GETCH_MODE, COMMAND_MODE, EXTERNAL_MODE};
+
+
+#define COLOR_STATUSBAR 32
+#define COLOR_COMMAND 33
+#define COLOR_OUTPUT 34
+#define COLOR_MSG 35
+#define COLOR_ERRMSG 36
+#define COLOR_WELCOME 37
+#define COLOR_SEPARATOR 38
+#define COLOR_TOP 39
+#define COLOR_BOT 40
+
+struct gui_command {
+       char *key;
+       char *name;
+       char *description;
+       void (*handler)(void);
+};
+
+struct stat_item {
+       char name[MAXLINE];
+       char prefix[MAXLINE];
+       char postfix[MAXLINE];
+       unsigned y;
+       unsigned x;
+       unsigned len;
+       int fg, bg;
+       int align;
+       char content[MAXLINE];
+};
+
+static struct gui_theme theme;
+
+int _argc;
+char **_argv;
+
+static void com_help(void);
+static void com_reread_conf(void);
+static void com_enlarge_top_win(void);
+static void com_shrink_top_win(void);
+static void com_version(void);
+static void com_quit(void);
+static void com_refresh(void);
+static void com_ll_incr(void);
+static void com_ll_decr(void);
+static void com_prev_theme(void);
+static void com_next_theme(void);
+static void com_scroll_up(void);
+static void com_scroll_down(void);
+static void com_page_up(void);
+static void com_page_down(void);
+
+struct gui_command command_list[] = {
+       {
+               .key = "?",
+               .name = "help",
+               .description = "print help",
+               .handler = com_help
+       }, {
+               .key = "+",
+               .name = "enlarge_win",
+               .description = "enlarge the top window",
+               .handler = com_enlarge_top_win
+       }, {
+               .key = "-",
+               .name = "shrink_win",
+               .description = "shrink the top window",
+               .handler = com_shrink_top_win
+       }, {
+               .key = "r",
+               .name = "reread_conf",
+               .description = "reread configuration file",
+               .handler = com_reread_conf
+       }, {
+               .key = "q",
+               .name = "quit",
+               .description = "exit para_gui",
+               .handler = com_quit
+       }, {
+               .key = "^L",
+               .name = "refresh",
+               .description = "redraw the screen",
+               .handler = com_refresh
+       }, {
+               .key = ".",
+               .name = "next_theme",
+               .description = "switch to next theme",
+               .handler = com_next_theme
+       }, {
+               .key = ",",
+               .name = "prev_theme",
+               .description = "switch to previous stream",
+               .handler = com_prev_theme
+       }, {
+               .key = ">",
+               .name = "ll_incr",
+               .description = "increase loglevel (decreases verbosity)",
+               .handler = com_ll_incr
+       }, {
+               .key = "<",
+               .name = "ll_decr",
+               .description = "decrease loglevel (increases verbosity)",
+               .handler = com_ll_decr
+       }, {
+               .key = "V",
+               .name = "version",
+               .description = "show the para_gui version",
+               .handler = com_version
+       }, {
+               .key = "<up>",
+               .name = "scroll_up",
+               .description = "scroll up one line",
+               .handler = com_scroll_up
+       }, {
+               .key = "<down>",
+               .name = "scroll_down",
+               .description = "scroll down one line",
+               .handler = com_scroll_down
+       }, {
+               .key = "<ppage>",
+               .name = "page_up",
+               .description = "scroll up one page",
+               .handler = com_page_up
+       }, {
+               .key = "<npage>",
+               .name = "page_down",
+               .description = "scroll down one page",
+               .handler = com_page_down
+       }, {
+               .handler = NULL
+       }
+};
+
+static int find_cmd_byname(char *name)
+{
+       int i;
+
+       for (i = 0; command_list[i].handler; i++)
+               if (!strcmp(command_list[i].name, name))
+                       return i;
+       return -1;
+}
+
+/* taken from mutt */
+static char *km_keyname(int c)
+{
+       static char buf[10];
+
+       if (c == KEY_UP) {
+               sprintf(buf, "<up>");
+               return buf;
+       }
+       if (c == KEY_DOWN) {
+               sprintf(buf, "<down>");
+               return buf;
+       }
+       if (c == KEY_LEFT) {
+               sprintf(buf, "<left>");
+               return buf;
+       }
+       if (c == KEY_RIGHT) {
+               sprintf(buf, "<right>");
+               return buf;
+       }
+       if (c == KEY_NPAGE) {
+               sprintf(buf, "<npage>");
+               return buf;
+       }
+       if (c == KEY_PPAGE) {
+               sprintf(buf, "<ppage>");
+               return buf;
+       }
+       if (c < 256 && c > -128 && iscntrl((unsigned char) c)) {
+               if (c < 0)
+                       c += 256;
+               if (c < 128) {
+                       buf[0] = '^';
+                       buf[1] = (c + '@') & 0x7f;
+                       buf[2] = 0;
+               } else
+                       snprintf(buf, sizeof(buf), "\\%d%d%d", c >> 6,
+                               (c >> 3) & 7, c & 7);
+       } else if (c >= KEY_F0 && c < KEY_F(256))
+               sprintf(buf, "<F%d>", c - KEY_F0);
+       else if (isprint(c))
+               snprintf(buf, sizeof(buf), "%c", (unsigned char) c);
+       else
+               snprintf(buf, sizeof(buf), "\\x%hx", (unsigned short) c);
+       return buf;
+}
+
+static char *configfile_exists(void)
+{
+       static char *config_file;
+       char *tmp;
+
+       if (!conf.config_file_given) {
+               if (!config_file) {
+                       char *home = para_homedir();
+                       config_file = make_message("%s/.paraslash/gui.conf",
+                               home);
+                       free(home);
+               }
+               tmp = config_file;
+       } else
+               tmp = conf.config_file_arg;
+       return file_exists(tmp)? tmp: NULL;
+}
+
+/*
+ * print num spaces to curses window
+ */
+static void add_spaces(WINDOW* win, unsigned int num)
+{
+       while (num > 0) {
+               num--;
+               waddstr(win, " ");
+       }
+}
+
+/*
+ * print aligned string to curses window. This function always prints
+ * exactly len chars.
+ */
+static int align_str(WINDOW* win, char *string, unsigned int len,
+               unsigned int align)
+{
+       int num; /* of spaces */
+       char *str;
+
+       if (!win || !string)
+               return -1;
+       num = len - strlen(string);
+       str = para_strdup(string);
+       if (num < 0) {
+               str[len] = '\0';
+               num = 0;
+       }
+       if (align == LEFT) {
+               waddstr(win, str);
+               add_spaces(win, num);
+       } else if (align == RIGHT) {
+               add_spaces(win, num);
+               waddstr(win, str);
+       } else {
+               add_spaces(win, num / 2);
+               waddstr(win, str[0]? str: "");
+               add_spaces(win, num - num / 2);
+       }
+       free(str);
+       return 1;
+}
+
+__printf_2_3 static void print_in_bar(int color, char *fmt,...)
+{
+       char *msg;
+
+       if (!curses_active)
+               return;
+       wattron(in.win, COLOR_PAIR(color));
+       PARA_VSPRINTF(fmt, msg);
+       wmove(in.win, 0, 0);
+       align_str(in.win, msg, sb.cols, LEFT);
+       free(msg);
+       wrefresh(in.win);
+}
+
+/*
+ * update the status bar
+ */
+static void print_status_bar(void)
+{
+       if (!curses_active)
+               return;
+       wmove(sb.win, 0, 0);
+       align_str(sb.win,STANDARD_STATUS_BAR, sb.cols, CENTER);
+       wrefresh(sb.win);
+}
+
+/*
+ * get the number of the oldest rbe that is (partially) visible. On return,
+ * lines contains the sum of the number of lines of all visable entries. If the
+ * first one is only partially visible, lines is greater than bot.lines.
+ */
+static int first_visible_rbe(unsigned *lines)
+{
+       int i;
+       *lines = 0;
+       for (i = scroll_position; i < RINGBUFFER_SIZE; i++) {
+               struct rb_entry *rbe = ringbuffer_get(bot_win_rb, i);
+               int rbe_lines;
+               if (!rbe)
+                       return i - 1;
+//             fprintf(stderr, "found: %s\n", rbe->msg);
+               rbe_lines = NUM_LINES(rbe->len);
+               if (rbe_lines > bot.lines)
+                       return -1;
+               *lines += rbe_lines;
+               if (*lines >= bot.lines)
+                       return i;
+       }
+       return RINGBUFFER_SIZE - 1;
+}
+
+static int draw_top_rbe(unsigned *lines)
+{
+       unsigned len;
+       int offset, fvr = first_visible_rbe(lines);
+       struct rb_entry *rbe;
+
+       if (fvr < 0)
+               return -1;
+       wmove(bot.win, 0, 0);
+       rbe = ringbuffer_get(bot_win_rb, fvr);
+       if (!rbe)
+               return -1;
+       /* first rbe might be only partially visible */
+       offset = (*lines - bot.lines) * bot.cols;
+       len = strlen(rbe->msg);
+       if (offset < 0 || len < offset)
+               return -1;
+       wattron(bot.win, COLOR_PAIR(rbe->color));
+       waddstr(bot.win, rbe->msg + offset);
+       *lines = NUM_LINES(len - offset);
+       return fvr;
+}
+
+static void redraw_bot_win(void)
+{
+       unsigned lines;
+       int i;
+
+       wmove(bot.win, 0, 0);
+       wclear(bot.win);
+       i = draw_top_rbe(&lines);
+       if (i <= 0)
+               goto out;
+       while (i > 0 && lines < bot.lines) {
+               struct rb_entry *rbe = ringbuffer_get(bot_win_rb, --i);
+               if (!rbe) {
+                       lines++;
+                       waddstr(bot.win, "\n");
+                       continue;
+               }
+               lines += NUM_LINES(rbe->len);
+               wattron(bot.win, COLOR_PAIR(rbe->color));
+               waddstr(bot.win, "\n");
+               waddstr(bot.win, rbe->msg);
+       }
+out:
+       wrefresh(bot.win);
+}
+
+static void rb_add_entry(int color, char *msg)
+{
+       struct rb_entry *old, *new = para_malloc(sizeof(struct rb_entry));
+       int x, y;
+       new->color = color;
+       new->len = strlen(msg);
+       new->msg = msg;
+       old = ringbuffer_add(bot_win_rb, new);
+//     fprintf(stderr, "added: %s\n", new->msg);
+       if (old) {
+               free(old->msg);
+               free(old);
+       }
+       if (scroll_position) {
+               /* discard current scrolling, like xterm does */
+               scroll_position = 0;
+               redraw_bot_win();
+               return;
+       }
+       wattron(bot.win, COLOR_PAIR(color));
+       getyx(bot.win, y, x);
+       if (y || x)
+               waddstr(bot.win, "\n");
+       waddstr(bot.win, msg);
+}
+
+/*
+ * print formated output to bot win and refresh
+ */
+__printf_2_3 static void outputf(int color, char* fmt,...)
+{
+       char *msg;
+
+       if (!curses_active)
+               return;
+       PARA_VSPRINTF(fmt, msg);
+       rb_add_entry(color, msg);
+       wrefresh(bot.win);
+}
+
+static void add_output_line(char *line)
+{
+       if (!curses_active)
+               return;
+       rb_add_entry(COLOR_OUTPUT, para_strdup(line));
+}
+
+void para_log(int ll, char *fmt,...)
+{
+       int color;
+       char *msg;
+
+       if (ll < conf.loglevel_arg || !curses_active)
+               return;
+       switch (ll) {
+               case DEBUG:
+               case INFO:
+               case NOTICE:
+                       color = COLOR_MSG;
+                       break;
+               default:
+                       color = COLOR_ERRMSG;
+       }
+       PARA_VSPRINTF(fmt, msg);
+       chop(msg);
+       rb_add_entry(color, msg);
+       wrefresh(bot.win);
+}
+
+static void setup_signal_handling(void)
+{
+       signal_pipe = para_signal_init();
+       para_install_sighandler(SIGINT);
+       para_install_sighandler(SIGTERM);
+       para_install_sighandler(SIGCHLD);
+       para_install_sighandler(SIGWINCH);
+       para_install_sighandler(SIGUSR1);
+       signal(SIGPIPE, SIG_IGN);
+       signal(SIGHUP, SIG_IGN);
+}
+
+static void do_exit(int ret)
+{
+       signal(SIGTERM, SIG_IGN);
+       kill(0, SIGTERM);
+       exit(ret);
+}
+
+static void shutdown_curses(void)
+{
+       if (!curses_active)
+               return;
+       def_prog_mode();
+       curses_active = 0;
+       endwin();
+}
+
+static void finish(int ret)
+{
+       shutdown_curses();
+       do_exit(ret);
+}
+
+/*
+ * exit curses and print given message to stdout/stderr
+ */
+__printf_2_3 static void msg_n_exit(int ret, char* fmt, ...)
+{
+       va_list argp;
+       FILE *outfd = ret? stderr: stdout;
+
+       shutdown_curses();
+       va_start(argp, fmt);
+       vfprintf(outfd, fmt, argp);
+       va_end(argp);
+       do_exit(ret);
+}
+
+static void print_welcome(void)
+{
+       int ll = conf.loglevel_arg;
+       if (ll > NOTICE)
+               return;
+       outputf(COLOR_WELCOME, "Welcome to para_gui " VERSION
+               " \"" CODENAME "\". Theme: %s", theme.name);
+       wclrtoeol(bot.win);
+}
+
+/*
+ * init all windows
+ */
+static void init_wins(int top_lines)
+{
+       int i;
+
+       top.lines = top_lines;
+       top.cols = COLS;
+       top.begy = 0;
+       top.begx = 0;
+
+       bot.lines = LINES - top.lines - 3;
+       bot.cols = COLS;
+       bot.begy = top.lines + 1;
+       bot.begx = 0;
+
+       sb.lines = 1;
+       sb.cols = COLS;
+       sb.begy = LINES - 2;
+       sb.begx = 0;
+
+       in.lines = 1;
+       in.cols = COLS;
+       in.begy = LINES - 1;
+       in.begx = 0;
+
+       sep.lines = 1;
+       sep.cols = COLS;
+       sep.begy = top.lines;
+       sep.begx = 0;
+
+       assume_default_colors(theme.default_fg, theme.default_bg);
+       if (top.win) {
+               mvwin(top.win, top.begy, top.begx);
+               wresize(top.win, top.lines, top.cols);
+
+               mvwin(sb.win, sb.begy, sb.begx);
+               wresize(sb.win, sb.lines, sb.cols);
+
+               mvwin(sep.win, sep.begy, sep.begx);
+               wresize(sep.win, sep.lines, sep.cols);
+
+               mvwin(bot.win, bot.begy, bot.begx);
+               wresize(bot.win, bot.lines, bot.cols);
+
+               mvwin(in.win, in.begy, in.begx);
+               wresize(in.win, in.lines, in.cols);
+       } else {
+               sep.win = newwin(sep.lines, sep.cols, sep.begy, sep.begx);
+               top.win = newwin(top.lines, top.cols, top.begy, top.begx);
+               bot.win = newwin(bot.lines, bot.cols, bot.begy, bot.begx);
+               sb.win = newwin(sb.lines, sb.cols, sb.begy, sb.begx);
+               in.win = newwin(in.lines, in.cols, in.begy, in.begx);
+               if (!top.win || !bot.win || !sb.win || !in.win || !sep.win)
+                       msg_n_exit(1, "Error: Cannot create curses windows\n");
+               wclear(bot.win);
+               wclear(sb.win);
+               wclear(in.win);
+               scrollok(bot.win, 1);
+               wattron(sb.win, COLOR_PAIR(COLOR_STATUSBAR));
+               wattron(sep.win, COLOR_PAIR(COLOR_SEPARATOR));
+               wattron(bot.win, COLOR_PAIR(COLOR_BOT));
+               wattron(top.win, COLOR_PAIR(COLOR_TOP));
+               nodelay(top.win, 1);
+               nodelay(bot.win, 1);
+               nodelay(sb.win, 1);
+               nodelay(in.win, 0);
+
+               keypad(top.win, 1);
+               keypad(bot.win, 1);
+               keypad(sb.win, 1);
+               keypad(in.win, 1);
+               print_status_bar();
+       }
+       wmove(sep.win, 0, 0);
+       for (i = 1; i <= COLS; i++)
+               waddstr(sep.win, theme.sep_str);
+       wclear(top.win);
+       //wclear(bot.win);
+       wnoutrefresh(top.win);
+       wnoutrefresh(bot.win);
+       //wnoutrefresh(sb.win);
+       print_status_bar();
+       wnoutrefresh(in.win);
+       wnoutrefresh(sep.win);
+       doupdate();
+}
+
+static void check_geometry(void)
+{
+       if (LINES < theme.lines_min || COLS < theme.cols_min)
+               msg_n_exit(EXIT_FAILURE, "Error: Terminal (%dx%d) too small"
+                       " (need at least %dx%d)\n", COLS, LINES,
+                       theme.cols_min, theme.lines_min);
+}
+
+/*
+ * Print stat item #i to curses window
+ */
+static void print_stat_item(int i)
+{
+       char *tmp;
+       struct stat_item_data d = theme.data[i];
+       char *c = stat_content[i];
+
+       if (!curses_active || !d.len || !c)
+               return;
+       tmp = make_message("%s%s%s", d.prefix, c, d.postfix);
+//     PARA_DEBUG_LOG("%s: read: %s\n", __func__, tmp);
+       wmove(top.win, d.y * top.lines / 100, d.x * COLS / 100);
+       wrefresh(top.win);
+       wattron(top.win, COLOR_PAIR(i + 1));
+       align_str(top.win, tmp, d.len * COLS / 100, d.align);
+       free(tmp);
+       wrefresh(top.win);
+}
+
+static void print_all_items(void)
+{
+       int i;
+
+       if (!curses_active)
+               return;
+       for (i = 0; i < NUM_STAT_ITEMS; i++)
+               print_stat_item(i);
+}
+static void clear_all_items(void)
+{
+       int i;
+
+       for (i = 0; i < NUM_STAT_ITEMS; i++) {
+               free(stat_content[i]);
+               stat_content[i] = para_strdup("");
+       }
+}
+
+static void init_colors(void)
+{
+       int i;
+       if (!has_colors())
+               msg_n_exit(EXIT_FAILURE, "Error: No color term\n");
+       start_color();
+       for (i = 0; i < NUM_STAT_ITEMS; i++)
+               if (theme.data[i].len)
+                       init_pair(i + 1, theme.data[i].fg, theme.data[i].bg);
+       init_pair(COLOR_STATUSBAR, theme.sb_fg, theme.sb_bg);
+       init_pair(COLOR_COMMAND, theme.cmd_fg, theme.cmd_bg);
+       init_pair(COLOR_OUTPUT, theme.output_fg, theme.output_bg);
+       init_pair(COLOR_MSG, theme.msg_fg, theme.msg_bg);
+       init_pair(COLOR_ERRMSG, theme.err_msg_fg, theme.err_msg_bg);
+       init_pair(COLOR_WELCOME, theme.welcome_fg, theme.welcome_bg);
+       init_pair(COLOR_SEPARATOR, theme.sep_fg, theme.sep_bg);
+       init_pair(COLOR_TOP, theme.default_fg, theme.default_bg);
+       init_pair(COLOR_BOT, theme.default_fg, theme.default_bg);
+}
+
+/*
+ * (re-)initialize the curses library  FIXME: Error checking
+ */
+static void init_curses(void)
+{
+       curses_active = 1;
+       if (top.win && refresh() == ERR) { /* refesh is really needed */
+               msg_n_exit(EXIT_FAILURE, "refresh() failed\n");
+       }
+       check_geometry();
+       curs_set(0); /* make cursor invisible, ignore errors */
+//     if (noraw() == ERR);
+//             msg_n_exit(EXIT_FAILURE, "can not place terminal out of "
+//                     "raw mode\n");
+       nonl(); /* tell curses not to do NL->CR/NL on output */
+       noecho(); /* don't echo input */
+       cbreak(); /* take input chars one at a time, no wait for \n */
+       //reset_prog_mode();
+       init_colors();
+       clear();
+       init_wins(theme.top_lines_default);
+       print_all_items();
+       noecho(); /* don't echo input */
+}
+
+
+static void check_sigchld(void)
+{
+       pid_t pid;
+reap_next_child:
+       pid = para_reap_child();
+       if (pid <= 0)
+               return;
+       if (pid == external_cmd_pid) {
+               external_cmd_pid = 0;
+               external_cmd_died = 1;
+       }
+       goto reap_next_child;
+}
+
+/*
+ * print status line if line starts with known command.
+ */
+static void check_stat_line(char *line)
+{
+       int i;
+
+//     PARA_INFO_LOG("%s: checking: %s\n", __func__, line);
+       i = stat_line_valid(line);
+       if (i >= 0) {
+               line += strlen(status_item_list[i]) + 1;
+               free(stat_content[i]);
+               stat_content[i] = para_strdup(line);
+               print_stat_item(i);
+       }
+       return;
+}
+
+/*
+ * This sucker modifies its first argument. *handler and *arg are
+ * pointers to 0-terminated strings (inside line). Crap.
+ */
+static int split_key_map(char *line, char **handler, char **arg)
+{
+       if (!(*handler = strchr(line + 1, ':')))
+               goto err_out;
+       **handler = '\0';
+       (*handler)++;
+       if (!(*arg = strchr(*handler, ':')))
+               goto err_out;
+       **arg = '\0';
+       (*arg)++;
+       return 1;
+err_out:
+       return 0;
+}
+
+static int check_key_map_args(void)
+{
+       char *s;
+       int i, ret = -1;
+       char *tmp = NULL, *handler, *arg;
+
+       for (i = 0; i < conf.key_map_given; ++i) {
+               s = conf.key_map_arg[i];
+               if (!(*s))
+                       goto err_out;
+               free(tmp);
+               tmp = para_strdup(s);
+               if (!split_key_map(tmp, &handler, &arg))
+                       goto err_out;
+               if (strlen(handler) != 1)
+                       goto err_out;
+               if (*handler != 'x'
+                       && *handler != 'd'
+                       && *handler != 'i'
+                       && *handler != 'p')
+                       goto err_out;
+               if (*handler != 'i')
+                       continue;
+               if (find_cmd_byname(arg) < 0)
+                       goto err_out;
+       }
+       ret = 0;
+err_out:
+       free(tmp);
+       return ret;
+}
+
+/*
+ * React to various signal-related events
+ */
+static void handle_signal(int sig)
+{
+       switch (sig) {
+       case SIGTERM:
+               msg_n_exit(EXIT_FAILURE,
+                       "only the good die young (caught SIGTERM))\n");
+               return;
+       case SIGWINCH:
+               if (curses_active) {
+                       shutdown_curses();
+                       init_curses();
+                       redraw_bot_win();
+               }
+               return;
+       case SIGINT:
+               PARA_WARNING_LOG("%s", "caught SIGINT, reset");
+               /* Nothing to do. SIGINT killed our child, para_client stat.
+                * This get noticed by do_select which resets everything
+                */
+               return;
+       case SIGUSR1:
+               PARA_NOTICE_LOG("%s", "got SIGUSR1, rereading configuration");
+               com_reread_conf();
+               return;
+       case SIGCHLD:
+               check_sigchld();
+               return;
+       }
+}
+
+/* open pipe if last attempt to open was more than 2 secs ago */
+static int open_audiod_pipe(void)
+{
+       static time_t open_time, *p;
+       time_t now;
+       double diff;
+
+       if (p) {
+               time(&now);
+               diff = difftime(now, open_time);
+               if (diff < 2)
+                       return -1;
+       } else
+               p = &open_time;
+       time(&open_time);
+       return para_open_audiod_pipe(conf.stat_cmd_arg);
+}
+
+
+/*
+ * This is the core select loop. Besides the (internal) signal
+ * pipe, the following other fds are checked according to the mode:
+ *
+ * GETCH_MODE: check stdin, return when key is pressed
+ *
+ * COMMAND_MODE: check command_pipe and stdin. Return when a screen full
+ * of output has been read or when other end has closed command pipe or
+ * when any key is pressed.
+ *
+ * EXTERNAL_MODE: Check only signal pipe. Used when an external command
+ * is running. During that thime curses is disabled.  Returns when
+ * external_cmd_pid == 0.
+ */
+static int do_select(int mode)
+{
+       fd_set rfds;
+       int ret;
+       int max_fileno, cp_numread = 1;
+       char command_buf[STRINGSIZE] = "";
+       int cbo = 0; /* command buf offset */
+       struct timeval tv;
+repeat:
+       tv.tv_sec = conf.timeout_arg  / 1000;
+       tv.tv_usec = (conf.timeout_arg % 1000) * 1000;
+//     ret = refresh_status();
+       FD_ZERO(&rfds);
+       max_fileno = 0;
+       /* audiod pipe */
+       if (audiod_pipe < 0)
+               audiod_pipe = open_audiod_pipe();
+       if (audiod_pipe >= 0) {
+               FD_SET(audiod_pipe, &rfds);
+               max_fileno = MAX(max_fileno, audiod_pipe);
+       }
+
+       /* signal pipe */
+       FD_SET(signal_pipe, &rfds);
+       max_fileno = MAX(max_fileno, signal_pipe);
+       /* command pipe only for COMMAND_MODE */
+       if (command_pipe >= 0 && mode == COMMAND_MODE) {
+               FD_SET(command_pipe, &rfds);
+               max_fileno = MAX(max_fileno, command_pipe);
+       }
+       FD_SET(STDIN_FILENO, &rfds);
+       ret = select(max_fileno + 1, &rfds, NULL, NULL, &tv);
+//     PARA_DEBUG_LOG("select returned %d\n", ret);
+
+       /* signals */
+       if (FD_ISSET(signal_pipe, &rfds)) {
+               int sig_nr = para_next_signal();
+               if (sig_nr > 0)
+                       handle_signal(sig_nr);
+       }
+       if (ret <= 0)
+               goto check_return; /* skip fd checks */
+       /* read command pipe if ready */
+       if (command_pipe >= 0 && mode == COMMAND_MODE &&
+                       FD_ISSET(command_pipe, &rfds)) {
+               cp_numread = read(command_pipe, command_buf + cbo,
+                       STRINGSIZE - 1 - cbo);
+               if (cp_numread >= 0)
+                       cbo += cp_numread;
+               else {
+                       if (cp_numread < 0)
+                               PARA_ERROR_LOG("read error (%d)", cp_numread);
+                       close(command_pipe);
+                       command_pipe = -1;
+               }
+       }
+       if (audiod_pipe >= 0 && FD_ISSET(audiod_pipe, &rfds))
+               if (read_audiod_pipe(audiod_pipe, check_stat_line) <= 0) {
+                       close(audiod_pipe);
+                       audiod_pipe = -1;
+                       clear_all_items();
+                       free(stat_content[SI_STATUS_BAR]);
+                       stat_content[SI_STATUS_BAR] = 
+                               para_strdup("audiod not running!?\n");
+                       print_all_items();
+                       sleep(1);
+               }
+check_return:
+       switch (mode) {
+       case COMMAND_MODE:
+               if (cp_numread <= 0 && !cbo) /* command complete */
+                       return 0;
+               if (cbo)
+                       cbo = for_each_line(command_buf, cbo, &add_output_line, 0);
+               if (cp_numread <= 0)
+                       cbo = 0;
+               wrefresh(bot.win);
+               break;
+       case GETCH_MODE:
+               ret = wgetch(top.win);
+               if (ret != ERR && ret != KEY_RESIZE)
+                       return ret;
+               break;
+       case EXTERNAL_MODE:
+               if (external_cmd_died) {
+                       external_cmd_died = 0;
+                       return 0;
+               }
+       }
+       goto repeat;
+}
+
+/*
+ * read from command pipe and print data to bot window
+ */
+static int send_output(void)
+{
+       if (command_pipe < 0)
+               return 0;
+       if (do_select(COMMAND_MODE) >= 0)
+               PARA_INFO_LOG("%s", "command complete");
+       else
+               PARA_NOTICE_LOG("%s", "command aborted");
+       print_in_bar(COLOR_MSG, " ");
+       return 1;
+}
+
+static int client_cmd_cmdline(char *cmd)
+{
+       pid_t pid;
+       int ret, fds[3] = {0, 1, 0};
+       char *c = make_message(BINDIR "/para_client %s", cmd);
+
+       outputf(COLOR_COMMAND, "%s", c);
+       print_in_bar(COLOR_MSG, "executing client command, hit q to abort\n");
+       ret = para_exec_cmdline_pid(&pid, c, fds);
+       free(c);
+       if (ret < 0)
+               return -1;
+       command_pipe = fds[1];
+       return send_output();
+}
+
+/*
+ * exec command and print output to bot win
+ */
+static int display_cmd(char *cmd)
+{
+       pid_t pid;
+       int fds[3] = {0, 1, 0};
+
+       print_in_bar(COLOR_MSG, "executing display command, hit q to abort");
+       outputf(COLOR_COMMAND, "%s", cmd);
+       if (para_exec_cmdline_pid(&pid, cmd, fds) < 0)
+               return -1;
+       command_pipe = fds[1];
+       return send_output();
+}
+
+/*
+ * shutdown curses and stat pipe before executing external commands
+ */
+static int external_cmd(char *cmd)
+{
+       int fds[3] = {-1, -1, -1};
+
+       if (external_cmd_pid)
+               return -1;
+       shutdown_curses();
+       para_exec_cmdline_pid(&external_cmd_pid, cmd, fds);
+       do_select(EXTERNAL_MODE);
+       init_curses();
+       return 0;
+}
+
+static void print_scroll_msg(void)
+{
+       unsigned lines_total, filled = ringbuffer_filled(bot_win_rb);
+       int first_rbe = first_visible_rbe(&lines_total);
+       print_in_bar(COLOR_MSG, "scrolled view: %d-%d/%d\n", filled - first_rbe,
+               filled - scroll_position, ringbuffer_filled(bot_win_rb));
+}
+
+static void com_page_down(void)
+{
+       unsigned lines = 0;
+       int i = scroll_position;
+       while (lines < bot.lines && --i > 0) {
+               struct rb_entry *rbe = ringbuffer_get(bot_win_rb, i);
+               if (!rbe)
+                       break;
+               lines += NUM_LINES(strlen(rbe->msg));
+       }
+       if (lines) {
+               scroll_position = i;
+               redraw_bot_win();
+               print_scroll_msg();
+               return;
+       }
+       print_in_bar(COLOR_ERRMSG, "bottom of buffer is shown\n");
+}
+
+static void com_page_up(void)
+{
+       unsigned lines;
+       int fvr = first_visible_rbe(&lines);
+       if (fvr < 0 || fvr + 1 >= ringbuffer_filled(bot_win_rb)) {
+               print_in_bar(COLOR_ERRMSG, "top of buffer is shown\n");
+               return;
+       }
+       scroll_position = fvr + 1;
+       for (; scroll_position > 0; scroll_position--) {
+               fvr = first_visible_rbe(&lines);
+               if (lines == bot.lines)
+                       break;
+       }
+       redraw_bot_win();
+       print_scroll_msg();
+}
+
+static void com_scroll_down(void)
+{
+       struct rb_entry *rbe;
+       int rbe_lines;
+
+       if (!scroll_position) {
+               print_in_bar(COLOR_ERRMSG, "bottom of buffer is shown\n");
+               return;
+       }
+       scroll_position--;
+       rbe = ringbuffer_get(bot_win_rb, scroll_position);
+       rbe_lines = NUM_LINES(rbe->len);
+       wscrl(bot.win, rbe_lines);
+       wmove(bot.win, bot.lines - rbe_lines, 0);
+       wattron(bot.win, COLOR_PAIR(rbe->color));
+       waddstr(bot.win, rbe->msg);
+       wrefresh(bot.win);
+       print_scroll_msg();
+}
+
+static void com_scroll_up(void)
+{
+       struct rb_entry *rbe = NULL;
+       unsigned lines;
+       int i, first_rbe, scroll;
+
+       /* the entry that is going to vanish */
+       rbe = ringbuffer_get(bot_win_rb, scroll_position);
+       if (!rbe)
+               goto err_out;
+       scroll = NUM_LINES(rbe->len);
+       first_rbe = first_visible_rbe(&lines);
+       if (first_rbe < 0 || (first_rbe == ringbuffer_filled(bot_win_rb) - 1))
+               goto err_out;
+       scroll_position++;
+       wscrl(bot.win, -scroll);
+       i = draw_top_rbe(&lines);
+       if (i < 0)
+               goto err_out;
+       while (i > 0 && lines < scroll) {
+               int rbe_lines;
+               rbe = ringbuffer_get(bot_win_rb, --i);
+               if (!rbe)
+                       break;
+               rbe_lines = NUM_LINES(rbe->len);
+               lines += rbe_lines;
+//             fprintf(stderr, "msg: %s\n", rbe->msg);
+               wattron(bot.win, COLOR_PAIR(rbe->color));
+               waddstr(bot.win, "\n");
+               waddstr(bot.win, rbe->msg);
+               if (!i)
+                       break;
+               i--;
+       }
+       wrefresh(bot.win);
+       print_scroll_msg();
+       return;
+err_out:
+       print_in_bar(COLOR_ERRMSG, "top of buffer is shown\n");
+}
+
+static void com_ll_decr(void)
+{
+       if (conf.loglevel_arg <= DEBUG) {
+               print_in_bar(COLOR_ERRMSG,
+                       "loglevel already at maximal verbosity\n");
+               return;
+       }
+       conf.loglevel_arg--;
+       print_in_bar(COLOR_MSG, "loglevel set to %d\n", conf.loglevel_arg);
+}
+
+static void com_ll_incr(void)
+{
+       if (conf.loglevel_arg >= EMERG) {
+               print_in_bar(COLOR_ERRMSG,
+                       "loglevel already at miminal verbosity\n");
+               return;
+       }
+       conf.loglevel_arg++;
+       print_in_bar(COLOR_MSG, "loglevel set to %d\n", conf.loglevel_arg);
+}
+
+/*
+ * reread configuration, terminate on errors
+ */
+static void com_reread_conf(void)
+{
+       char *cf =configfile_exists();
+
+       if (!cf) {
+               PARA_WARNING_LOG("%s", "there is no configuration to read");
+               return;
+       }
+       PARA_INFO_LOG("%s", "rereading command line options and config file");
+       cmdline_parser(_argc, _argv, &conf);
+       cmdline_parser_configfile(cf, &conf, 1, 1, 0);
+       PARA_NOTICE_LOG("%s", "configuration read");
+       if (check_key_map_args() < 0)
+               finish(EXIT_FAILURE);
+}
+
+static void com_help(void)
+{
+       int i;
+
+       for (i = 0; i < conf.key_map_given; ++i) {
+               char *s = conf.key_map_arg[i], *handler, *arg,
+                       *desc = "", tmp[MAXLINE];
+
+               strcpy(tmp, s);
+               if (!split_key_map(tmp, &handler, &arg))
+                       return;
+               switch(*handler) {
+                       case 'i':
+                               handler = "internal";
+                               desc = command_list[find_cmd_byname(arg)].description;
+                               break;
+                       case 'x': handler = "external"; break;
+                       case 'd': handler = "display "; break;
+                       case 'p': handler = "para    "; break;
+               }
+               outputf(COLOR_MSG, "%s\t%s\t%s%s\t%s", tmp, handler, arg,
+                       strlen(arg) < 8? "\t" : "",
+                       desc);
+       }
+       for (i = 0; command_list[i].handler; i++) {
+               struct gui_command gc = command_list[i];
+
+               outputf(COLOR_MSG, "%s\tinternal\t%s\t%s%s", gc.key, gc.name,
+                       strlen(gc.name) < 8? "\t" : "",
+                       gc.description);
+       }
+       print_in_bar(COLOR_MSG, "try \"para_gui -h\" or \"para_client help\" "
+               "for more info");
+       return;
+}
+
+static void com_shrink_top_win(void)
+{
+       if (top.lines <= theme.top_lines_min) {
+               PARA_WARNING_LOG("%s", "can not decrease top window");
+               return;
+       }
+       init_wins(top.lines - 1);
+       wclear(top.win);
+       print_all_items();
+       print_in_bar(COLOR_MSG, "%s", "decreased top window");
+}
+
+static void com_enlarge_top_win(void)
+{
+       if (bot.lines < 3) {
+               PARA_WARNING_LOG("%s", "can not increase top window");
+               return;
+       }
+       init_wins(top.lines + 1);
+       wclear(top.win);
+       print_all_items();
+       print_in_bar(COLOR_MSG, "increased top window");
+}
+
+static void com_version(void)
+{
+       print_in_bar(COLOR_MSG, "para_gui " VERSION " \"" CODENAME "\"");
+}
+
+static void com_quit(void)
+{
+       finish(0);
+}
+
+static void com_refresh(void)
+{
+       shutdown_curses();
+       init_curses();
+}
+
+static void change_theme(int next)
+{
+       if (next)
+               next_theme(&theme);
+       else
+               prev_theme(&theme);
+       /* This seems to be needed twice, why? */
+       com_refresh();
+       com_refresh();
+       PARA_NOTICE_LOG("new theme: %s", theme.name);
+}
+
+static void com_next_theme(void)
+{
+       change_theme(1);
+}
+
+static void com_prev_theme(void)
+{
+       change_theme(0);
+}
+
+
+static void handle_command(int c)
+{
+       int i;
+
+       /* first check user's key bindings */
+       for (i = 0; i < conf.key_map_given; ++i) {
+               char tmp[MAXLINE], *handler, *arg;
+
+               strcpy(tmp, conf.key_map_arg[i]);
+               if (!split_key_map(tmp, &handler, &arg))
+                       return;
+               if (!strcmp(tmp, km_keyname(c))) {
+                       if (*handler == 'd') {
+                               display_cmd(arg);
+                               return;
+                       }
+                       if (*handler == 'x') {
+                               external_cmd(arg);
+                               return;
+                       }
+                       if (*handler == 'p') {
+                               client_cmd_cmdline(arg);
+                               return;
+                       }
+                       if (*handler == 'i') {
+                               int num = find_cmd_byname(arg);
+                               if (num >= 0)
+                                       command_list[num].handler();
+                               return;
+                       }
+               }
+       }
+       /* not found, check internal key bindings */
+       for (i = 0; command_list[i].handler; i++) {
+               if (!strcmp(km_keyname(c), command_list[i].key)) {
+                       command_list[i].handler();
+                       return;
+               }
+       }
+       print_in_bar(COLOR_ERRMSG, "key '%s' is not bound, press ? for help",
+               km_keyname(c));
+}
+
+int main(int argc, char *argv[])
+{
+       int ret;
+       char *cf;
+
+       _argc = argc;
+       _argv = argv;
+
+       if (cmdline_parser(argc, argv, &conf)) {
+               fprintf(stderr, "parse error while reading command line\n");
+               exit(EXIT_FAILURE);
+       }
+       init_theme(0, &theme);
+       top.lines = theme.top_lines_default;
+       if (check_key_map_args() < 0) {
+               fprintf(stderr, "invalid key map\n");
+               exit(EXIT_FAILURE);
+       }
+       cf = configfile_exists();
+       if (!cf && conf.config_file_given) {
+               fprintf(stderr, "can not read config file %s\n",
+                       conf.config_file_arg);
+               exit(EXIT_FAILURE);
+       }
+       if (cf)
+               cmdline_parser_configfile(cf, &conf, 0, 0, 0);
+       if (check_key_map_args() < 0) {
+               fprintf(stderr, "invalid key map in config file\n");
+               exit(EXIT_FAILURE);
+       }
+       setup_signal_handling();
+       bot_win_rb = ringbuffer_new(RINGBUFFER_SIZE);
+       initscr(); /* needed only once, always successful */
+       init_curses();
+       print_welcome();
+       for (;;) {
+               print_status_bar();
+               ret = do_select(GETCH_MODE);
+               if (!ret)
+                       continue;
+               print_in_bar(COLOR_MSG, " ");
+               handle_command(ret);
+       }
+}
diff --git a/gui.conf.sample b/gui.conf.sample
new file mode 100644 (file)
index 0000000..6c4873b
--- /dev/null
@@ -0,0 +1,42 @@
+# modify to suit your needs
+
+# jump around in current song with F1-F10
+key_map "^:p:jmp 0"
+key_map "<F1>:p:jmp 10"
+key_map "<F2>:p:jmp 20"
+key_map "<F3>:p:jmp 30"
+key_map "<F4>:p:jmp 40"
+key_map "<F5>:p:jmp 50"
+key_map "<F6>:p:jmp 60"
+key_map "<F7>:p:jmp 70"
+key_map "<F8>:p:jmp 80"
+key_map "<F9>:p:jmp 90"
+key_map "<F10>:p:jmp 97"
+
+#key mappings for standard options
+key_map "s:p:play"
+key_map "$:p:next"
+key_map "p:p:pause"
+key_map " :p:stop"
+
+# jump forward and backward in current song with j,k,J,K
+key_map "k:p:ff 10"
+key_map "j:p:ff 10-"
+key_map "K:p:ff 60"
+key_map "J:p:ff 60-"
+
+# misc stuff
+key_map "l:p:last"
+key_map "u:p:uptime"
+key_map "A:p:laa"
+key_map "S:p:streams"
+
+# shell escapes
+key_map "!:x:/bin/bash"
+key_map "::x:para_client"
+
+# display commands
+# key_map "Q:d:aumix -q"
+
+# do something like this to control volume
+#key_map "5:d:aumix -v 50"
diff --git a/gui.ggo b/gui.ggo
new file mode 100644 (file)
index 0000000..2f5dfc2
--- /dev/null
+++ b/gui.ggo
@@ -0,0 +1,9 @@
+section "general options"
+option "auto_decode" a "auto-decode audio stream" flag on
+option "config_file" c "(default='~/.paraslash/gui.conf')" string typestr="filename" no
+option "loglevel" l "set loglevel (0-6)" int typestr="level" default="4" no
+option "timeout" t "set timeout" int typestr="milliseconds" default="300" no
+option "stat_cmd" s "command to read server and audiod status data from" string typestr="command" default="para_audioc -t 100 stat" no
+
+section "mapping keys to commands"
+option "key_map" k "Map key k to command c using mode m. Mode may be d, x or p for display, external and paraslash commands, respectively. Of course, this option may be given multiple times, one for each key mapping." string typestr="k:m:c" no multiple
diff --git a/gui_common.c b/gui_common.c
new file mode 100644 (file)
index 0000000..2c49912
--- /dev/null
@@ -0,0 +1,29 @@
+#include "para.h"
+
+extern const char *status_item_list[NUM_STAT_ITEMS];
+
+
+int para_open_audiod_pipe(char *cmd)
+{
+       int fds[3] = {0, 1, 0};
+       pid_t pid;
+       return para_exec_cmdline_pid(&pid, cmd, fds) > 0?
+               fds[1] : -1;
+}
+
+int read_audiod_pipe(int fd, void (*line_handler)(char *) )
+{
+       static char buf[STRINGSIZE + 1];
+       static ssize_t loaded, bufsize = sizeof(buf);
+       ssize_t ret;
+
+       if (loaded >= bufsize)
+               loaded = 0;
+       ret = read(fd, buf + loaded, bufsize - loaded);
+       if (ret > 0) {
+               loaded += ret;
+               buf[loaded] = '\0';
+               loaded = for_each_line(buf, loaded, line_handler, 0);
+       }
+       return ret;
+}
diff --git a/gui_theme.c b/gui_theme.c
new file mode 100644 (file)
index 0000000..87d8e05
--- /dev/null
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 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.
+ */
+
+#include "para.h"
+#include <curses.h>
+
+#define NUM_THEMES 2
+
+
+static int current_theme_num;
+
+static void init_theme_simple(struct gui_theme *t)
+{
+       struct stat_item_data *d = t->data;
+       t->name = "simple";
+       t->author = "Andre Noll";
+       t->lines_min = 5;
+       t->top_lines_min = 2;
+       t->cols_min = 40;
+       t->top_lines_default = 2;
+       t->sb_bg = COLOR_CYAN;
+       t->sb_fg = COLOR_BLACK;
+       t->cmd_bg = COLOR_WHITE;
+       t->cmd_fg = COLOR_BLACK;
+       t->output_bg = COLOR_BLUE;
+       t->output_fg = COLOR_WHITE;
+       t->msg_bg = COLOR_BLUE;
+       t->msg_fg = COLOR_YELLOW;
+       t->err_msg_bg = COLOR_RED;
+       t->err_msg_fg = COLOR_WHITE;
+       t->welcome_bg = COLOR_BLUE;
+       t->welcome_fg = COLOR_WHITE;
+       t->sep_bg = COLOR_BLUE;
+       t->sep_fg = COLOR_CYAN;
+       t->default_fg = COLOR_WHITE;
+       t->default_bg = COLOR_BLUE;
+       t->sep_str = "*";
+
+       d[SI_STATUS_BAR].prefix = "";
+       d[SI_STATUS_BAR].postfix = "";
+       d[SI_STATUS_BAR].fg = COLOR_WHITE;
+       d[SI_STATUS_BAR].bg = COLOR_BLUE;
+       d[SI_STATUS_BAR].align = CENTER;
+       d[SI_STATUS_BAR].x = 0;
+       d[SI_STATUS_BAR].y = 7;
+       d[SI_STATUS_BAR].len = 100;
+
+       d[SI_STATUS].prefix = "para_server: ";
+       d[SI_STATUS].postfix = "";
+       d[SI_STATUS].fg = COLOR_WHITE;
+       d[SI_STATUS].bg = COLOR_BLUE;
+       d[SI_STATUS].align = CENTER;
+       d[SI_STATUS].x = 0;
+       d[SI_STATUS].y = 60;
+       d[SI_STATUS].len = 50;
+
+       d[SI_AUDIOD_STATUS].prefix = "para_audiod: ";
+       d[SI_AUDIOD_STATUS].postfix = "";
+       d[SI_AUDIOD_STATUS].fg = COLOR_WHITE;
+       d[SI_AUDIOD_STATUS].bg = COLOR_BLUE;
+       d[SI_AUDIOD_STATUS].align = CENTER;
+       d[SI_AUDIOD_STATUS].x = 50;
+       d[SI_AUDIOD_STATUS].y = 60;
+       d[SI_AUDIOD_STATUS].len = 50;
+
+}
+
+static void init_theme_colorful_blackness(struct gui_theme *t)
+{
+       struct stat_item_data *d = t->data;
+       t->name = "colorful blackness";
+       t->author = "Andre Noll";
+       /* minimal number of lines that is needed to display all
+        * information provided by this theme
+        */
+       t->lines_min = 15;
+       t->cols_min = 80;
+       t->top_lines_min = 9;
+       t->top_lines_default = 11; /* default number of lines */
+
+       t->sb_bg = COLOR_GREEN; /* status bar background */
+       t->sb_fg = COLOR_BLACK; /* status bar foreground */
+       t->cmd_bg = COLOR_BLACK;
+       t->cmd_fg = COLOR_YELLOW;
+       t->output_bg = COLOR_BLACK;
+       t->output_fg = COLOR_CYAN;
+       t->msg_bg = COLOR_BLACK;
+       t->msg_fg = COLOR_WHITE;
+       t->err_msg_bg = COLOR_RED;
+       t->err_msg_fg = COLOR_WHITE;
+       t->welcome_bg = COLOR_BLUE;
+       t->welcome_fg = COLOR_WHITE;
+       t->sep_bg = COLOR_BLACK; /* color of the separator */
+       t->sep_fg = COLOR_BLUE;
+       t->sep_str = "-";
+       t->default_bg = COLOR_BLACK;
+       t->default_fg = COLOR_MAGENTA;
+
+
+       d[SI_PLAY_TIME].prefix = "";
+       d[SI_PLAY_TIME].postfix = "";
+       d[SI_PLAY_TIME].fg = COLOR_CYAN;
+       d[SI_PLAY_TIME].bg = COLOR_BLACK;
+       d[SI_PLAY_TIME].align = CENTER;
+       d[SI_PLAY_TIME].x = 0;
+       d[SI_PLAY_TIME].y = 7;
+       d[SI_PLAY_TIME].len = 35;
+
+       d[SI_STATUS_BAR].prefix = "";
+       d[SI_STATUS_BAR].postfix = "";
+       d[SI_STATUS_BAR].fg = COLOR_CYAN;
+       d[SI_STATUS_BAR].bg = COLOR_BLACK;
+       d[SI_STATUS_BAR].align = LEFT;
+       d[SI_STATUS_BAR].x = 35;
+       d[SI_STATUS_BAR].y = 7;
+       d[SI_STATUS_BAR].len = 65;
+
+       d[SI_STATUS].prefix = "";
+       d[SI_STATUS].postfix = " ";
+       d[SI_STATUS].fg = COLOR_RED;
+       d[SI_STATUS].bg = COLOR_BLACK;
+       d[SI_STATUS].align = RIGHT;
+       d[SI_STATUS].x = 0;
+       d[SI_STATUS].y = 17;
+       d[SI_STATUS].len = 11;
+
+       d[SI_STATUS_FLAGS].prefix = "(";
+       d[SI_STATUS_FLAGS].postfix = ")";
+       d[SI_STATUS_FLAGS].fg = COLOR_RED;
+       d[SI_STATUS_FLAGS].bg = COLOR_BLACK;
+       d[SI_STATUS_FLAGS].align = LEFT;
+       d[SI_STATUS_FLAGS].x = 11;
+       d[SI_STATUS_FLAGS].y = 17;
+       d[SI_STATUS_FLAGS].len = 10;
+
+       d[SI_DBTOOL].prefix = "dbtool: ";
+       d[SI_DBTOOL].postfix = "";
+       d[SI_DBTOOL].fg = COLOR_RED;
+       d[SI_DBTOOL].bg = COLOR_BLACK;
+       d[SI_DBTOOL].align = CENTER;
+       d[SI_DBTOOL].x = 21;
+       d[SI_DBTOOL].y = 17;
+       d[SI_DBTOOL].len = 20;
+
+       d[SI_FORMAT].prefix = "format: ";
+       d[SI_FORMAT].postfix = "";
+       d[SI_FORMAT].fg = COLOR_RED;
+       d[SI_FORMAT].bg = COLOR_BLACK;
+       d[SI_FORMAT].align = CENTER;
+       d[SI_FORMAT].x = 42;
+       d[SI_FORMAT].y = 17;
+       d[SI_FORMAT].len = 14;
+
+
+       d[SI_NUM_PLAYED].prefix = "#";
+       d[SI_NUM_PLAYED].postfix = "";
+       d[SI_NUM_PLAYED].fg = COLOR_RED;
+       d[SI_NUM_PLAYED].bg = COLOR_BLACK;
+       d[SI_NUM_PLAYED].align = CENTER;
+       d[SI_NUM_PLAYED].x = 60;
+       d[SI_NUM_PLAYED].y = 17;
+       d[SI_NUM_PLAYED].len = 15;
+
+       d[SI_UPTIME].prefix = "SUp: ";
+       d[SI_UPTIME].postfix = "";
+       d[SI_UPTIME].fg = COLOR_RED;
+       d[SI_UPTIME].bg = COLOR_BLACK;
+       d[SI_UPTIME].align = CENTER;
+       d[SI_UPTIME].x = 75;
+       d[SI_UPTIME].y = 17;
+       d[SI_UPTIME].len = 25;
+
+       d[SI_AUDIOD_STATUS].prefix = "audiod: ";
+       d[SI_AUDIOD_STATUS].postfix = "";
+       d[SI_AUDIOD_STATUS].fg = COLOR_MAGENTA;
+       d[SI_AUDIOD_STATUS].bg = COLOR_BLACK;
+       d[SI_AUDIOD_STATUS].align = RIGHT;
+       d[SI_AUDIOD_STATUS].x = 0;
+       d[SI_AUDIOD_STATUS].y = 27;
+       d[SI_AUDIOD_STATUS].len = 13;
+
+
+       d[SI_DECODER_FLAGS].prefix = "[";
+       d[SI_DECODER_FLAGS].postfix = "]";
+       d[SI_DECODER_FLAGS].fg = COLOR_MAGENTA;
+       d[SI_DECODER_FLAGS].bg = COLOR_BLACK;
+       d[SI_DECODER_FLAGS].align = LEFT;
+       d[SI_DECODER_FLAGS].x = 14;
+       d[SI_DECODER_FLAGS].y = 27;
+       d[SI_DECODER_FLAGS].len = 8;
+
+       d[SI_MTIME].prefix = "Mtime: ";
+       d[SI_MTIME].postfix = "";
+       d[SI_MTIME].fg = COLOR_MAGENTA;
+       d[SI_MTIME].bg = COLOR_BLACK;
+       d[SI_MTIME].align = CENTER;
+       d[SI_MTIME].x = 22;
+       d[SI_MTIME].y = 27;
+       d[SI_MTIME].len = 30;
+
+       d[SI_FILE_SIZE].prefix = "Size: ";
+       d[SI_FILE_SIZE].postfix = " kb";
+       d[SI_FILE_SIZE].fg = COLOR_MAGENTA;
+       d[SI_FILE_SIZE].bg = COLOR_BLACK;
+       d[SI_FILE_SIZE].align = CENTER;
+       d[SI_FILE_SIZE].x = 52;
+       d[SI_FILE_SIZE].y = 27;
+       d[SI_FILE_SIZE].len = 26;
+
+       d[SI_AUDIOD_UPTIME].prefix = "AUp: ";
+       d[SI_AUDIOD_UPTIME].postfix = "";
+       d[SI_AUDIOD_UPTIME].fg = COLOR_MAGENTA;
+       d[SI_AUDIOD_UPTIME].bg = COLOR_BLACK;
+       d[SI_AUDIOD_UPTIME].align = CENTER;
+       d[SI_AUDIOD_UPTIME].x = 78;
+       d[SI_AUDIOD_UPTIME].y = 27;
+       d[SI_AUDIOD_UPTIME].len = 22;
+
+
+       d[SI_AUDIO_INFO1].prefix = "";
+       d[SI_AUDIO_INFO1].postfix = "";
+       d[SI_AUDIO_INFO1].fg = COLOR_GREEN;
+       d[SI_AUDIO_INFO1].bg = COLOR_BLACK;
+       d[SI_AUDIO_INFO1].align = CENTER;
+       d[SI_AUDIO_INFO1].x = 0;
+       d[SI_AUDIO_INFO1].y = 43;
+       d[SI_AUDIO_INFO1].len = 100;
+
+       d[SI_AUDIO_INFO2].prefix = "";
+       d[SI_AUDIO_INFO2].postfix = "";
+       d[SI_AUDIO_INFO2].fg = COLOR_GREEN;
+       d[SI_AUDIO_INFO2].bg = COLOR_BLACK;
+       d[SI_AUDIO_INFO2].align = CENTER;
+       d[SI_AUDIO_INFO2].x = 0;
+       d[SI_AUDIO_INFO2].y = 53;
+       d[SI_AUDIO_INFO2].len = 100;
+
+       d[SI_AUDIO_INFO3].prefix = "";
+       d[SI_AUDIO_INFO3].postfix = "";
+       d[SI_AUDIO_INFO3].fg = COLOR_GREEN;
+       d[SI_AUDIO_INFO3].bg = COLOR_BLACK;
+       d[SI_AUDIO_INFO3].align = CENTER;
+       d[SI_AUDIO_INFO3].x = 0;
+       d[SI_AUDIO_INFO3].y = 63;
+       d[SI_AUDIO_INFO3].len = 100;
+
+       d[SI_DBINFO1].prefix = "";
+       d[SI_DBINFO1].postfix = "";
+       d[SI_DBINFO1].fg = COLOR_YELLOW;
+       d[SI_DBINFO1].bg = COLOR_BLACK;
+       d[SI_DBINFO1].align = CENTER;
+       d[SI_DBINFO1].x = 0;
+       d[SI_DBINFO1].y = 77;
+       d[SI_DBINFO1].len = 100;
+
+       d[SI_DBINFO2].prefix = "";
+       d[SI_DBINFO2].postfix = "";
+       d[SI_DBINFO2].fg = COLOR_YELLOW;
+       d[SI_DBINFO2].bg = COLOR_BLACK;
+       d[SI_DBINFO2].align = CENTER;
+       d[SI_DBINFO2].x = 0;
+       d[SI_DBINFO2].y = 87;
+       d[SI_DBINFO2].len = 100;
+
+       d[SI_DBINFO3].prefix = "";
+       d[SI_DBINFO3].postfix = "";
+       d[SI_DBINFO3].fg = COLOR_YELLOW;
+       d[SI_DBINFO3].bg = COLOR_BLACK;
+       d[SI_DBINFO3].align = CENTER;
+       d[SI_DBINFO3].x = 0;
+       d[SI_DBINFO3].y = 97;
+       d[SI_DBINFO3].len = 100;
+}
+
+void init_theme(int num, struct gui_theme *t)
+{
+       int i;
+
+       for (i = 0; i < NUM_STAT_ITEMS; i++)
+               t->data[i].len = 0;
+       current_theme_num = num;
+
+       return (num % NUM_THEMES)?
+               init_theme_simple(t) : init_theme_colorful_blackness(t);
+}
+
+void prev_theme(struct gui_theme *t)
+{
+       return init_theme(++current_theme_num, t);
+}
+
+void next_theme(struct gui_theme *t)
+{
+       return init_theme(--current_theme_num, t);
+}
+
diff --git a/http.h b/http.h
new file mode 100644 (file)
index 0000000..5708c7b
--- /dev/null
+++ b/http.h
@@ -0,0 +1,3 @@
+/** \file http.h macros used by http_send and http_recv */
+#define HTTP_OK_MSG "HTTP/1.0 200 OK\r\n\r\n"
+#define HTTP_GET_MSG "GET / HTTP/1.0"
diff --git a/http_recv.c b/http_recv.c
new file mode 100644 (file)
index 0000000..2e3a11b
--- /dev/null
@@ -0,0 +1,217 @@
+/*
+ * 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 http_recv.c paraslash's http receiver */
+
+#include "para.h"
+
+#include "http.h"
+#include "recv.h"
+#include "http_recv.cmdline.h"
+#include "error.h"
+#include "net.h"
+#include "string.h"
+
+/** the output buffer size of the http receiver */
+#define BUFSIZE (32 * 1024)
+
+/**
+ * the possible states of a http receiver node
+ *
+ * \sa receiver_node
+ */
+enum http_recv_status {HTTP_CONNECTED, HTTP_SENT_GET_REQUEST, HTTP_STREAMING};
+
+/**
+ * data specific to the http receiver
+ *
+ * Each running instance of the http receiver reserves space for one such struct.
+ */
+struct private_http_recv_data {
+/**
+ *
+ *
+ * the current status of the http receiver node
+ *
+ * It gets initialized to #HTTP_CONNECTED by the open function of the
+ * http receiver.
+ *
+ * \sa receiver::open, receiver_node.
+ */
+       enum http_recv_status status;
+/**
+ *
+ *
+ * the file descriptor used for receiving the http stream
+ *
+ * The pre_select function of the http receiver adds this file descriptor to
+ * the set of file decriptors which are checked for reading/writing (depending
+ * on the current status) by the select loop of the application (para_audiod or
+ * para_recv).
+ *
+ * The post_select function of the http receiver uses \a fd, if ready, to
+ * establish the http connection, and updates \a status according to the new
+ * state of the connection.  As soon as \a status is #HTTP_STREAMING, \a fd is
+ * going to be only checked for reading. If data is available, it is read into
+ * the output buffer of the receiver node by post_select.
+ *
+ * \sa receiver::pre_select receiver::post_select receiver_node
+ */
+       int fd;
+};
+
+static void http_shutdown(void)
+{
+       return;
+}
+
+static char *make_request_msg(void)
+{
+       char *ret, *hn = para_hostname();
+       ret = make_message("%s\nHost: %s\nUser-Agent: para_recv/%s\n\n\n",
+               HTTP_GET_MSG, hn, VERSION);
+       free(hn);
+       return ret;
+}
+
+static int http_pre_select(struct receiver_node *rn, fd_set *rfds, fd_set *wfds,
+       __unused struct timeval *timeout)
+{
+       struct private_http_recv_data *phd = rn->private_data;
+
+       if  (phd->status == HTTP_CONNECTED)
+               FD_SET(phd->fd, wfds);
+       else
+               FD_SET(phd->fd, rfds);
+       return phd->fd;
+}
+
+static int http_post_select(struct receiver_node *rn, int select_ret,
+               fd_set *rfds, fd_set *wfds)
+{
+       int ret;
+       struct private_http_recv_data *phd = rn->private_data;
+
+       if (!select_ret) /* we're not interested in timeouts */
+               return 1;
+       if  (phd->status == HTTP_CONNECTED) {
+               char *rq;
+               if (!FD_ISSET(phd->fd, wfds))
+                       return 1; /* nothing to do */
+               rq = make_request_msg();
+               PARA_NOTICE_LOG("%s", "sending http request\n");
+               ret = send_va_buffer(phd->fd, "%s", rq);
+               free(rq);
+               if (ret < 0)
+                       return E_SEND_HTTP_REQUEST;
+               phd->status = HTTP_SENT_GET_REQUEST;
+               return 1;
+       }
+       if (!FD_ISSET(phd->fd, rfds))
+               return 1; /* nothing to do */
+       if (phd->status == HTTP_SENT_GET_REQUEST) {
+               ret = recv_pattern(phd->fd, HTTP_OK_MSG, MAXLINE);
+               if (ret < 0)
+                       return -E_MISSING_OK;
+               PARA_NOTICE_LOG("%s", "received ok msg, streaming\n");
+               phd->status = HTTP_STREAMING;
+               return 1;
+       }
+       /* already streaming */
+       if (rn->loaded >= BUFSIZE) {
+               PARA_ERROR_LOG("%s", "buffer overrun\n");
+               return -E_OVERRUN;
+       }
+       ret = recv_bin_buffer(phd->fd, rn->buf + rn->loaded, BUFSIZE - rn->loaded);
+       if (ret <= 0) {
+               PARA_NOTICE_LOG("recv returned %d/%d\n", ret, BUFSIZE - rn->loaded);
+               return ret < 0? -E_HTTP_RECV_BUF : 0;
+       }
+       rn->loaded += ret;
+       return 1;
+}
+
+static void http_recv_close(struct receiver_node *rn)
+{
+       struct private_http_recv_data *phd = rn->private_data;
+       close(phd->fd);
+       free(rn->buf);
+       free(rn->private_data);
+}
+
+static void *http_recv_parse_config(int argc, char **argv)
+{
+       struct gengetopt_args_info *tmp = para_calloc(sizeof(struct gengetopt_args_info));
+
+       if (!http_recv_cmdline_parser(argc, argv, tmp))
+               return tmp;
+       free(tmp);
+       return NULL;
+}
+
+static int http_recv_open(struct receiver_node *rn)
+{
+       struct private_http_recv_data *phd;
+       struct hostent *he;
+       struct gengetopt_args_info *conf = rn->conf;
+       struct sockaddr_in their_addr;
+       int ret;
+
+       rn->buf = para_calloc(BUFSIZE);
+       rn->private_data = para_calloc(sizeof(struct private_http_recv_data));
+       phd = rn->private_data;
+       optind = 0;
+       ret = -E_HOST_INFO;
+       if (!(he = get_host_info(conf->host_arg)))
+               goto err_out;
+       /* get new socket */
+       ret = -E_SOCKET;
+       if ((phd->fd = get_socket()) < 0)
+               goto err_out;
+       /* init their_addr */
+       init_sockaddr(&their_addr, conf->port_arg, he);
+       /* connect */
+       PARA_NOTICE_LOG("connecting to %s:%d\n", conf->host_arg,
+               conf->port_arg);
+       ret = para_connect(phd->fd, &their_addr);
+       if (ret < 0)
+               goto err_out;
+       phd->status = HTTP_CONNECTED;
+       return 1;
+err_out:
+       free(rn->private_data);
+       free(rn->buf);
+       return ret;
+}
+
+/**
+ * the init function of the http receiver
+ *
+ * \param r pointer to the receiver struct to initialize
+ *
+ * Just initialize all function pointers of \a r.
+ */
+void http_recv_init(struct receiver *r)
+{
+       r->open = http_recv_open;
+       r->close = http_recv_close;
+       r->pre_select = http_pre_select;
+       r->post_select = http_post_select;
+       r->shutdown = http_shutdown;
+       r->parse_config = http_recv_parse_config;
+}
diff --git a/http_recv.ggo b/http_recv.ggo
new file mode 100644 (file)
index 0000000..5c4ef4e
--- /dev/null
@@ -0,0 +1,2 @@
+option "host" i "ip or host" string default="localhost" no
+option "port" p "tcp port to connect to" int default="8000" no
diff --git a/http_send.c b/http_send.c
new file mode 100644 (file)
index 0000000..c574ba3
--- /dev/null
@@ -0,0 +1,586 @@
+/*
+ * 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 http_send.c paraslash's http sender */
+
+
+#include "server.cmdline.h"
+#include "server.h"
+#include "http.h"
+#include "afs.h"
+#include "send.h"
+#include "list.h"
+#include "close_on_fork.h"
+#include "error.h"
+#include "net.h"
+#include "string.h"
+
+/** \cond convert sock_addr_in to ascii */
+#define CLIENT_ADDR(hc) inet_ntoa((hc)->addr.sin_addr)
+/* get the port number of a struct http_client */
+#define CLIENT_PORT(hc) (hc)->addr.sin_port
+#define HTTP_ERR_MSG "HTTP/1.0 400 Bad Request\n"
+/** \endcond */
+
+extern struct gengetopt_args_info conf;
+
+/** the possible states of a client from the server's POV */
+enum http_status {
+       HTTP_CONNECTED,
+       HTTP_GOT_GET_REQUEST,
+       HTTP_SENT_OK_MSG,
+       HTTP_READY_TO_STREAM,
+       HTTP_STREAMING,
+       HTTP_INVALID_GET_REQUEST
+};
+
+/** clients will be kicked if there are more than that many bytes pending */
+#define MAX_BACKLOG 40000
+/** the list of connected clients **/
+static struct list_head clients;
+/** the whitelist/blacklist */
+static struct list_head access_perm_list;
+
+/** describes one client that connected the tcp port of the http sender */
+struct http_client {
+/** the file descriptor of the client */
+       int fd;
+/** address information about the client */
+       struct sockaddr_in addr;
+/** the client's current status */
+       enum http_status status;
+/** non-zero if we included \a fd in the read set */
+       int check_r;
+/** non-zero if we included \a fd in the write set */
+       int check_w;
+/** the position of this client in the client list */
+       struct list_head node;
+/** the list of pending packets for this client */
+       struct list_head packet_queue;
+/** the number of pending bytes for this client */
+       unsigned long pq_bytes;
+};
+
+/**
+ * describes one queued data packet for a client
+ *
+ * The send function of the http sender checks each client fd for writing. If a
+ * client fd is not ready, it tries to queue that packet for this client until
+ * the number of queued bytes exceeds \p MAX_BACKLOG.
+ */
+struct queued_packet {
+/** the length of the packet in bytes */
+       unsigned int len;
+/** pointer to the packet data */
+       char *packet;
+/** position of the packet in the packet list */
+       struct list_head node;
+};
+
+/**
+ * describes one entry in the blacklist/whitelist of the http sender
+ */
+struct access_info {
+       /** the address to be black/whitelisted */
+       struct in_addr addr;
+       /** the netmask for this entry */
+       int netmask;
+       /** the position of this entry in the access_perm_list */
+       struct list_head node;
+};
+
+static int server_fd = -1, numclients;
+static struct sender *self;
+
+
+static void http_shutdown_client(struct http_client *hc, const char *msg)
+{
+       struct queued_packet *qp, *tmp;
+       PARA_INFO_LOG("shutting down %s on fd %d (%s)\n", CLIENT_ADDR(hc),
+               hc->fd, msg);
+       numclients--;
+       close(hc->fd);
+
+       list_for_each_entry_safe(qp, tmp, &hc->packet_queue, node) {
+               free(qp->packet);
+               list_del(&qp->node);
+               free(qp);
+       }
+       list_del(&hc->node);
+       free(hc);
+       return;
+}
+
+static void http_shutdown_clients_real(void)
+{
+       struct http_client *hc, *tmp;
+       list_for_each_entry_safe(hc, tmp, &clients, node)
+               http_shutdown_client(hc, "afs request");
+}
+static void http_shutdown_clients(void)
+{
+       struct http_client *hc, *tmp;
+       list_for_each_entry_safe(hc, tmp, &clients, node)
+               if (hc->status == HTTP_STREAMING)
+                       http_shutdown_client(hc, "afs request");
+}
+
+static int http_send_msg(struct http_client *hc, const char *msg)
+{
+       int ret = send_buffer(hc->fd, msg);
+
+       if (ret < 0)
+               http_shutdown_client(hc, "send msg failed");
+       return ret;
+}
+
+static void http_send_ok_msg(struct http_client *hc)
+{
+       PARA_INFO_LOG("sending http ok message to fd %d\n", hc->fd);
+       http_send_msg(hc, HTTP_OK_MSG);
+}
+
+static int http_send_err_msg(struct http_client *hc)
+{
+       PARA_NOTICE_LOG("sending bad request message to fd %d\n", hc->fd);
+       return http_send_msg(hc, HTTP_ERR_MSG);
+}
+
+static int queue_packet(struct http_client *hc, const char *buf, size_t len)
+{
+       struct queued_packet *qp;
+       if (hc->pq_bytes + len > MAX_BACKLOG) {
+               http_shutdown_client(hc, "packet queue overrun");
+               return -E_QUEUE;
+       }
+       qp = para_malloc(sizeof(struct queued_packet));
+       hc->pq_bytes += len;
+       qp->packet = para_malloc(len);
+       memcpy(qp->packet, buf, len);
+       qp->len = len;
+       list_add_tail(&qp->node, &hc->packet_queue);
+       PARA_INFO_LOG("%lu bytes queued for fd %d\n", hc->pq_bytes, hc->fd);
+       return 1;
+}
+
+static int write_ok(int fd)
+{
+       struct timeval tv = {0, 0};
+       fd_set wfds;
+       int ret;
+again:
+       FD_ZERO(&wfds);
+       FD_SET(fd, &wfds);
+       ret = select(fd + 1, NULL, &wfds, NULL, &tv);
+       if (ret < 0 && errno == EINTR)
+               goto again;
+       if (ret < 0)
+               ret = -E_WRITE_OK;
+       return ret;
+}
+
+
+static int send_queued_packets(struct http_client *hc)
+{
+       int ret;
+       struct queued_packet *qp, *tmp;
+
+       if (list_empty(&hc->packet_queue))
+               return 1;
+       list_for_each_entry_safe(qp, tmp, &hc->packet_queue, node) {
+               ret = write_ok(hc->fd);
+               if (ret <= 0)
+                       return ret;
+               ret = write(hc->fd, qp->packet, qp->len);
+               if (ret < 0)
+                       return ret;
+               if (ret != qp->len) {
+                       qp->len -= ret;
+                       memmove(qp->packet, qp->packet + ret, qp->len);
+                       return 0;
+               }
+               hc->pq_bytes -= qp->len;
+               free(qp->packet);
+               list_del(&qp->node);
+               free(qp);
+       }
+       return 1;
+}
+
+static void http_send(__unused struct audio_format *af,
+               long unsigned current_chunk,
+               __unused long unsigned chunks_sent, const char *buf, size_t len)
+{
+       struct http_client *hc, *tmp;
+       int ret;
+
+       list_for_each_entry_safe(hc, tmp, &clients, node) {
+               if (hc->status != HTTP_STREAMING &&
+                               hc->status != HTTP_READY_TO_STREAM)
+                       continue;
+               if (hc->status == HTTP_READY_TO_STREAM) {
+                       if (af->get_header_info && current_chunk) {
+                               /* need to send header */
+                               int hlen;
+                               char *buf = af->get_header_info(&hlen);
+                               if (!buf || hlen <= 0)
+                                       continue; /* header not yet available */
+                               PARA_INFO_LOG("queueing header: %d\n", hlen);
+                               if (queue_packet(hc, buf, hlen) < 0)
+                                       continue;
+                       } else
+                               PARA_INFO_LOG("%s", "no need to queue header\n");
+                       hc->status = HTTP_STREAMING;
+               }
+               ret = send_queued_packets(hc);
+               if (ret < 0) {
+                       http_shutdown_client(hc, "send error");
+                       continue;
+               }
+               if (!len)
+                       continue;
+               if (!ret || write_ok(hc->fd) <= 0) {
+                       PARA_INFO_LOG("fd %d not ready (%lu bytes queued),"
+                               " trying to queue packet\n", hc->fd,
+                               hc->pq_bytes);
+                       queue_packet(hc, buf, len);
+                       continue;
+               }
+//             PARA_DEBUG_LOG("sending %d -> %s\n", len, CLIENT_ADDR(hc));
+               ret = write(hc->fd, buf, len);
+               if (ret < 0) {
+                       http_shutdown_client(hc, "send error");
+                       continue;
+               }
+               if (ret != len)
+                       queue_packet(hc, buf + ret, len - ret);
+       }
+}
+
+static int host_in_access_perm_list(struct http_client *hc)
+{
+       struct access_info *ai, *tmp;
+       list_for_each_entry_safe(ai, tmp, &access_perm_list, node) {
+               unsigned mask = ((~0) >> ai->netmask);
+               if ((hc->addr.sin_addr.s_addr & mask) == (ai->addr.s_addr & mask))
+                       return 1;
+       }
+       return 0;
+}
+
+static void http_post_select(__unused struct audio_format *af, fd_set *rfds,
+               fd_set *wfds)
+{
+       int i = -1, match;
+       struct http_client *hc, *tmp;
+       char *err_msg;
+
+       list_for_each_entry_safe(hc, tmp, &clients, node) {
+               i++;
+//             PARA_DEBUG_LOG("handling client %d: %s\n", i, CLIENT_ADDR(hc));
+               switch (hc->status) {
+               case HTTP_STREAMING: /* nothing to do */
+               case HTTP_READY_TO_STREAM:
+                       break;
+               case HTTP_CONNECTED: /* need to recv get request */
+                       if (hc->check_r && FD_ISSET(hc->fd, rfds)) {
+                               if (recv_pattern(hc->fd, HTTP_GET_MSG, MAXLINE)
+                                               < 0) {
+                                       hc->status = HTTP_INVALID_GET_REQUEST;
+                               } else {
+                                       hc->status = HTTP_GOT_GET_REQUEST;
+                                       PARA_INFO_LOG("%s",
+                                               "received get request\n");
+                               }
+                       }
+                       break;
+               case HTTP_GOT_GET_REQUEST: /* need to send ok msg */
+                       if (hc->check_w && FD_ISSET(hc->fd, wfds)) {
+                               hc->status = HTTP_SENT_OK_MSG;
+                               http_send_ok_msg(hc);
+                       }
+                       break;
+               case HTTP_INVALID_GET_REQUEST: /* need to send err msg */
+                       if (hc->check_w && FD_ISSET(hc->fd, wfds)) {
+                               if (http_send_err_msg(hc) >= 0)
+                                       http_shutdown_client(hc,
+                                               "invalid get request");
+                       }
+                       break;
+               case HTTP_SENT_OK_MSG: /* need to send header? */
+                       if (hc->check_w && FD_ISSET(hc->fd, wfds))
+                               hc->status = HTTP_READY_TO_STREAM;
+                       break;
+               }
+       }
+       if (!FD_ISSET(server_fd, rfds))
+               return;
+       hc = para_calloc(sizeof(struct http_client));
+       err_msg = "accept error";
+       hc->fd = para_accept(server_fd, &hc->addr, sizeof(struct sockaddr_in));
+       if (hc->fd <= 0)
+               goto err_out;
+       PARA_NOTICE_LOG("connection from %s (fd %d)\n", CLIENT_ADDR(hc), hc->fd);
+       if (conf.http_max_clients_arg > 0 && numclients >=
+                       conf.http_max_clients_arg) {
+               err_msg = "server full";
+               goto err_out;
+       }
+       match = host_in_access_perm_list(hc);
+       PARA_DEBUG_LOG("host_in_access_perm_list: %d\n", match);
+       if ((match && !conf.http_default_deny_given) ||
+                       (!match && conf.http_default_deny_given)) {
+               err_msg = "permission denied";
+               goto err_out;
+       }
+       hc->status = HTTP_CONNECTED;
+       INIT_LIST_HEAD(&hc->packet_queue);
+       PARA_INFO_LOG("accepted client #%d: %s (fd %d)\n", numclients,
+               CLIENT_ADDR(hc), hc->fd);
+       numclients++;
+       list_add(&hc->node, &clients);
+       return;
+err_out:
+       PARA_WARNING_LOG("ignoring connect request from %s (%s)\n",
+               CLIENT_ADDR(hc), err_msg);
+       if (hc->fd > 0)
+               close(hc->fd);
+       free(hc);
+}
+
+static void http_pre_select(struct audio_format *af, int *max_fileno, fd_set *rfds,
+               fd_set *wfds)
+{
+       struct http_client *hc, *tmp;
+
+       if (server_fd < 0)
+               return;
+       FD_SET(server_fd, rfds);
+       *max_fileno = MAX(*max_fileno, server_fd);
+       list_for_each_entry_safe(hc, tmp, &clients, node) {
+               //PARA_DEBUG_LOG("hc %p on fd %d: status %d\n", hc, hc->fd, hc->status);
+               hc->check_r = 0;
+               hc->check_w = 0;
+               switch (hc->status) {
+               case HTTP_STREAMING:
+               case HTTP_READY_TO_STREAM:
+                       break;
+               case HTTP_CONNECTED: /* need to recv get request */
+                       FD_SET(hc->fd, rfds);
+                       *max_fileno = MAX(*max_fileno, hc->fd);
+                       hc->check_r = 1;
+                       break;
+               case HTTP_GOT_GET_REQUEST: /* need to send ok msg */
+               case HTTP_INVALID_GET_REQUEST: /* need to send err msg */
+                       FD_SET(hc->fd, wfds);
+                       *max_fileno = MAX(*max_fileno, hc->fd);
+                       hc->check_w = 1;
+                       break;
+               case HTTP_SENT_OK_MSG:
+                       if (!af || !afs_playing())
+                               break; /* wait until server starts playing */
+                       FD_SET(hc->fd, wfds);
+                       *max_fileno = MAX(*max_fileno, hc->fd);
+                       hc->check_w = 1;
+                       break;
+               }
+       }
+}
+
+static int open_tcp_port(int port)
+{
+       server_fd = init_tcp_socket(port);
+       if (server_fd < 0) {
+               http_shutdown_clients_real();
+               self->status = SENDER_OFF;
+               return server_fd;
+       }
+       self->status = SENDER_ON;
+       add_close_on_fork_list(server_fd);
+       return 1;
+}
+
+static int http_com_on(__unused struct sender_command_data *scd)
+{
+       if (self->status == SENDER_ON)
+               return 1;
+       return open_tcp_port(conf.http_port_arg);
+}
+
+static int http_com_off(__unused struct sender_command_data *scd)
+{
+       self->status = SENDER_OFF;
+       if (server_fd > 0) {
+               close(server_fd);
+               del_close_on_fork_list(server_fd);
+               server_fd = -1;
+       }
+       http_shutdown_clients_real();
+       return 1;
+}
+
+static void del_perm_list_entry(struct sender_command_data *scd)
+{
+       struct access_info *ai, *tmp;
+
+       list_for_each_entry_safe(ai, tmp, &access_perm_list, node) {
+               char *nad = para_strdup(inet_ntoa(ai->addr));
+               if (!strcmp(nad, inet_ntoa(scd->addr)) &&
+                               ai->netmask == scd->netmask) {
+                       PARA_NOTICE_LOG("removing %s/%i from access list\n",
+                               nad, ai->netmask);
+                       list_del(&ai->node);
+                       free(ai);
+               }
+               free(nad);
+       }
+}
+
+static void add_perm_list_entry(struct sender_command_data *scd)
+{
+       struct access_info *ai = para_malloc(sizeof(struct access_info));
+       ai->addr = scd->addr;
+       ai->netmask = scd->netmask;
+       PARA_INFO_LOG("adding %s/%i to access list\n", inet_ntoa(ai->addr),
+               ai->netmask);
+       list_add(&ai->node, &access_perm_list);
+}
+
+static int http_com_deny(struct sender_command_data *scd)
+{
+       if (conf.http_default_deny_given)
+               del_perm_list_entry(scd);
+       else
+               add_perm_list_entry(scd);
+       return 1;
+}
+
+static int http_com_allow(struct sender_command_data *scd)
+{
+       if (conf.http_default_deny_given)
+               add_perm_list_entry(scd);
+       else
+               del_perm_list_entry(scd);
+       return 1;
+}
+
+static char *http_info(void)
+{
+       char *clnts = NULL, *ap = NULL, *ret;
+       struct access_info *ai, *tmp_ai;
+       struct http_client *hc, *tmp_hc;
+
+       list_for_each_entry_safe(ai, tmp_ai, &access_perm_list, node) {
+               char *tmp = make_message("%s%s/%d ", ap? ap : "",
+                       inet_ntoa(ai->addr), ai->netmask);
+               free(ap);
+               ap = tmp;
+       }
+       list_for_each_entry_safe(hc, tmp_hc, &clients, node) {
+               char *tmp = make_message("%s%s:%d ", clnts? clnts : "",
+                       CLIENT_ADDR(hc), CLIENT_PORT(hc));
+               free(clnts);
+               clnts = tmp;
+       }
+       ret = make_message(
+               "http status: %s\n"
+               "http tcp port: %d\n"
+               "http clients: %d\n"
+               "http maximal number of clients: %d%s\n"
+               "http connected clients: %s\n"
+               "http access %s list: %s\n",
+               (self->status == SENDER_ON)? "on" : "off",
+               conf.http_port_arg,
+               numclients,
+               conf.http_max_clients_arg,
+               conf.http_max_clients_arg > 0? "" : " (unlimited)",
+               clnts? clnts : "(none)",
+               conf.http_default_deny_given? "allow" : "deny",
+               ap? ap : "(none)"
+       );
+       free(ap);
+       free(clnts);
+       return ret;
+}
+
+static void init_access_control_list(void)
+{
+       int i;
+       struct sender_command_data scd;
+
+       INIT_LIST_HEAD(&access_perm_list);
+       for (i = 0; i < conf.http_access_given; i++) {
+               char *arg = para_strdup(conf.http_access_arg[i]);
+               char *p = strchr(arg, '/');
+               if (!p)
+                       goto err;
+               *p = '\0';
+               if (!inet_aton(arg, &scd.addr))
+                       goto err;
+               scd.netmask = atoi(++p);
+               if (scd.netmask < 0 || scd.netmask > 32)
+                       goto err;
+               add_perm_list_entry(&scd);
+               goto success;
+err:
+               PARA_CRIT_LOG("syntax error for http_access option "
+                       "#%d, ignoring\n", i);
+success:
+               free(arg);
+               continue;
+       }
+}
+
+static char *http_help(void)
+{
+       return make_message(
+               "usage: {on|off}\n"
+               "usage: {allow|deny} IP mask\n"
+               "example: allow 127.0.0.1 32\n"
+       );
+}
+
+/**
+ * the init function of the http sender
+ *
+ * \param s pointer to the http sender struct
+ *
+ * It initializes all function pointers of \a s, init the client list and the
+ * acess control list as well. If autostart is wanted, open the tcp port.
+ */
+void http_send_init(struct sender *s)
+{
+       INIT_LIST_HEAD(&clients);
+       s->info = http_info;
+       s->send = http_send;
+       s->pre_select = http_pre_select;
+       s->post_select = http_post_select;
+       s->shutdown_clients = http_shutdown_clients;
+       s->help = http_help;
+       s->client_cmds[SENDER_ON] = http_com_on;
+       s->client_cmds[SENDER_OFF] = http_com_off;
+       s->client_cmds[SENDER_DENY] = http_com_deny;
+       s->client_cmds[SENDER_ALLOW] = http_com_allow;
+       s->client_cmds[SENDER_ADD] = NULL;
+       s->client_cmds[SENDER_DELETE] = NULL;
+       self = s;
+       init_access_control_list();
+       if (!conf.http_no_autostart_given)
+               open_tcp_port(conf.http_port_arg);
+       PARA_DEBUG_LOG("%s", "http sender init complete\n");
+}
diff --git a/index.html b/index.html
new file mode 100644 (file)
index 0000000..acb329c
--- /dev/null
@@ -0,0 +1,263 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+   "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+       <meta http-equiv='Content-Type' content='text/html; charset=iso-8859-1'>
+       <title>Paraslash</title>
+       <style type="text/css">
+               body {
+                       background-color: #000000;
+                       color: #cccccc;
+               }
+               :link { color: #990000 }
+               :visited { color: #990000 }
+               td.c2 {font-family: arial, helvetica, sans-serif; font-size: 80%}
+               td.c1 {font-family: lucida, helvetica; font-size: 248%}
+               a:hover {background:#ff0;}
+               a:hover img {background:#fff;}
+       </style>
+       <link rel="shortcut icon" href="paraslash.ico">
+</head>
+<body>
+       <basefont face="lucida, helvetica, arial" size="3">
+       <table border="0" cellpadding="0" cellspacing="0">
+       <tr>
+               <td>
+                       <a href="/"><IMG SRC="paraslash.png" alt="paraslash" border="0"></a><BR>
+               </td>
+               <td>
+                       <h3>Paraslash: Play, archive, rate and stream
+                       large audio sets happily</h3>
+
+                       A set of tools for doing just what its name
+                       suggests.
+               </td>
+       </tr>
+       <tr>
+               <td valign="TOP">
+                       <br><a href="#feature_list">Feature list</a>
+                       <br><a href="#screenshots">Screenshots</a>
+                       <br><a href="#download">Download</a>
+                       <br><a href="#live_demo">Live Demo</a>
+                       <br><a href="#changes">Changes</a> <br><a
+                       href="#documentation">Documentation</a>
+                       <br><a href="#license">License</a> <br><a
+                       href="#author">Author</a>
+               </td>
+               <td Valign="TOP">
+       <hr>
+                       <h3>Events</h3>
+                       <ul>
+                               <li>2005-12-27: paraslash-0.2.7 "transparent invariance"</li>
+                               <li>2005-10-29: paraslash-0.2.6 "recursive compensation"</li>
+                               <li>2005-10-27: <a href="doc/html/index.html">manual pages</a> online</li>
+                               <li>2005-10-13: paraslash-0.2.5 "aggressive resolution"</li>
+                               <li>2005-09-21: paraslash-0.2.4 "toxic anticipation"</li>
+                               <li>2005-09-01: paraslash-0.2.3 "hydrophilic movement"</li>
+                               <li>2005-08-19: paraslash-0.2.2 "tangential excitation"</li>
+                               <li>2005-08-15: paraslash-0.2.1 "surreal experience"</li>
+                               <li>2005-08-06: overview.pdf</li>
+                               <li>2005-08-06: paraslash-0.2.0 "distributed diffusion"</li>
+                               <li>2005-08-01: paraslash live stream</li>
+                               <li>2005-04-18: paraslash-0.1.7 "melting penetration"</li>
+                               <li>2005-03-05: paraslash-0.1.6 "asymptotic balance"</li>
+                               <li>2004-12-31: paraslash-0.1.5 "opaque eternity"</li>
+                               <li>2004-12-19: paraslash-0.1.4 "tunneling transition"</li>
+                               <li>2004-12-10: paraslash-0.1.3 "vanishing inertia"</li>
+                               <li>2004-11-28: paraslash-0.1.2 "spherical fluctuation"</li>
+                               <li>2004-11-05: paraslash-0.1.1 "floating atmosphere"</li>
+                               <li>2004-10-22: paraslash-0.1.0 "rotating cortex"</li>
+                       </ul>
+                       <hr>
+                       <h3><a name="feature_list">Feature list</a></h3>
+                       <ul>
+                               <li>network audio streaming software</li>
+                               <li>client/server tcp-networking</li>
+                               <li>command line interface</li>
+                               <li> <a href="http://www.openssl.org/">openssl</a> user authentication</li>
+                               <li>several grafical user interfaces</li>
+                               <li> <a href="http://www.mysql.com">mysql</a>-based audio file selector</li>
+                       </ul>
+                       See <a href="FEATURES.html">FEATURES</a> for a more detailed list.
+                       <hr>
+
+       <h3><a name="screenshots">Screenshots</a></h3>
+       Everybody loves screenshots, so
+               <a href="screenshots/">here</a>
+       we go.
+       <hr>
+
+       <h3><a name="download">Download</a></h3>
+
+               <p> Only <a href="versions/">source</a> is available,
+               including the <a href="versions/paraslash-cvs.tar.bz2">
+               nightly cvs snapshot</a>. All regular releases are
+               <a href="PUBLIC_KEY">cryptographically signed</a>.
+               Anonymous (read-only) cvs access is also
+               available. Checkout a copy with </p>
+
+               <p> cvs -d
+               :pserver:anonymous@cvs.systemlinux.org:/var/lib/cvs
+               login </p>
+               <p>(empty passwd)</p>
+               <p> cvs -d :pserver:anonymous@cvs.systemlinux.org:/var/lib/cvs co paraslash </p>
+
+
+               <p> Finally, you can <a href="HTML/index.html">RTFS online</a>.
+       <hr>
+
+       <h3><a name="live_demo">Live Demo</a></h3>
+
+       <p> There is a public paraslash stream at www.paraslash.org,
+       streaming
+
+               <a href="http://www.digitalvibes.de/">the music of Henri Petterson</a>.
+
+
+       You can listen to the stream with any mp3 player that supports
+       http streaming. Both </p>
+
+               <p>mpg123 http://www.paraslash.org:8009/</p>
+
+       and
+
+               <p>xmms http://www.paraslash.org:8009/</p>
+
+       <p> are known to work.</p>
+
+       <p> Moreover, there is an anonymous paraslash account
+       available which you can use to have a look at paraslash
+       without configuring and running para_server on your own box.
+       Just download and run
+
+               <a href="demo-script">this shell script</a>
+
+       on your Unix system. If you prefer to do things manually,
+       simply cut-and-paste the instructions given below verbatim
+       to your shell. No root-privileges are required.</p>
+
+<ul>
+               <li>
+                       Check that both aplay and mpg123 are installed on your system
+       </li>
+
+               <li>
+                       <a href="versions">Download </a>
+                       a recent paraslash package. You
+                       you need paraslash-0.2.0 or later
+                       for the demo, paraslash-0.1.x will not work.
+               </li>
+
+               <li>
+
+                       Install the neccessary paraslash binaries
+                       (you can safely ignore any warnings about
+                       missing software):
+
+                       <ul>
+                               <li>tar xjf paraslash-cvs.tar.bz2</li>
+                               <li>cd paraslash-cvs</li>
+                               <li>bin="para_client para_audioc para_audiod para_gui" # all we need
+                               <li>(./configure --prefix="$HOME" && make $bin) > /dev/null</li>
+                               <li> mkdir -p $HOME/bin; cp $bin $HOME/bin
+                               <li> export PATH=$HOME/bin:$PATH
+
+                       </ul>
+                       There should be no errors.
+               </li>
+               <li>
+                       Get the key for the anonymous account on
+                       www.paraslash.org:
+
+                       <ul>
+                               <li> dir="$HOME/.paraslash"; server=www.paraslash.org</li>
+                               <li> mkdir -p $dir</li>
+                               <li> wget --directory-prefix=$dir http://$server/key.anonymous</li>
+                       </ul>
+               </li>
+               <li>
+                       Tell para_client that we want to connect to
+                       www.paraslash.org as user anonymous:
+
+                       <ul>
+                       <li> conf="$dir/client.conf"; socket="$dir/socket"</li>
+                       <li> echo user \"anonymous\" > $conf</li>
+                       <li> echo hostname \"$server\" >> $conf</li>
+                       <li> echo key_file \"$dir/key.anonymous\" >> $conf</li>
+                       <li> echo socket \"$socket\" >> $dir/audioc.conf</li>
+                       </ul>
+               </li>
+               <li>
+                       Start para_audiod
+                       <ul> <li>
+
+                               para_audiod -d --stream_read_cmd
+                               "mp3:mpg123 -s http://$server:8009/"
+                               --stream_write_cmd "mp3:aplay -fcd" -L $dir/audiod.log
+                               --socket=$socket
+
+                       </li> </ul>
+               </li><li>
+                       Start para_gui
+                       <ul> <li>
+                               para_gui
+                       </li> </ul>
+               </li>
+       </ul> <hr>
+
+
+       <h3><a name="changes">Changes</a></h3>
+       Read the complete
+               <a href="ChangeLog.html">ChangeLog</a>
+       or the file
+               <a href="NEWS.html">NEWS</a>
+       containing a brief summary of the changes for each version.
+       <hr>
+
+
+       <h3><a name="documentation">Documentation</a></h3>
+       Have a look at this
+               <a href="overview.pdf">overview</a>,
+       a pdf file containing a sketch which illustrates how the pieces of paraslash work
+       together. Read
+               <a href="README.html">README</a>
+       for general information,
+               <a href="INSTALL.html">INSTALL</a>
+       for installation notes, and
+               <a href="README.mysql.html">README.mysql</a>
+       for instructions on how to use the mysql database tool
+       shipped with paraslash. There is also an online version
+       of paraslash's
+               <a href="doc/html/index.html">manual pages</a>.
+       <hr>
+
+       <h3><a name="license">License</a></h3>
+       Distribution of Paraslash is covered by the GNU GPL. See file
+               <a href="COPYING.html">COPYING</a>.
+       <hr>
+
+       <h3><a name="author">Author</a></h3>
+       Andr&eacute; Noll,
+               <a href="mailto:maan@systemlinux.org"
+               >maan@systemlinux.org</a>
+       <p>
+       Several people helped by reporting bugs, improving documentation,
+       constructive discussions, or, last but not least, by writing
+       free software on which this project is based on. See
+               <a href="CREDITS.html">CREDITS</a>
+       for an incomplete list of people.
+       <hr>
+
+
+       Last modified:
+       <!--#flastmod virtual="" -->
+       <p>
+       <a href="http://validator.w3.org/check?uri=referer"><img border="0"
+         src="http://www.w3.org/Icons/valid-html401"
+         alt="Valid HTML 4.01!" height="31" width="88"></a>
+       </p>
+
+       </td>
+       </table>
+</body>
+</html>
diff --git a/install-sh b/install-sh
new file mode 100755 (executable)
index 0000000..6ce63b9
--- /dev/null
@@ -0,0 +1,294 @@
+#!/bin/sh
+#
+# install - install a program, script, or datafile
+#
+# This originates from X11R5 (mit/util/scripts/install.sh), which was
+# later released in X11R6 (xc/config/util/install.sh) with the
+# following copyright and license.
+#
+# Copyright (C) 1994 X Consortium
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC-
+# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+# Except as contained in this notice, the name of the X Consortium shall not
+# be used in advertising or otherwise to promote the sale, use or other deal-
+# ings in this Software without prior written authorization from the X Consor-
+# tium.
+#
+#
+# FSF changes to this file are in the public domain.
+#
+# Calling this script install-sh is preferred over install.sh, to prevent
+# `make' implicit rules from creating a file called install from it
+# when there is no Makefile.
+#
+# This script is compatible with the BSD install script, but was written
+# from scratch.  It can only install one file at a time, a restriction
+# shared with many OS's install programs.
+
+
+# set DOITPROG to echo to test this script
+
+# Don't use :- since 4.3BSD and earlier shells don't like it.
+doit="${DOITPROG-}"
+
+
+# put in absolute paths if you don't have them in your path; or use env. vars.
+
+mvprog="${MVPROG-mv}"
+cpprog="${CPPROG-cp}"
+chmodprog="${CHMODPROG-chmod}"
+chownprog="${CHOWNPROG-chown}"
+chgrpprog="${CHGRPPROG-chgrp}"
+stripprog="${STRIPPROG-strip}"
+rmprog="${RMPROG-rm}"
+mkdirprog="${MKDIRPROG-mkdir}"
+
+transformbasename=""
+transform_arg=""
+instcmd="$mvprog"
+chmodcmd="$chmodprog 0755"
+chowncmd=""
+chgrpcmd=""
+stripcmd=""
+rmcmd="$rmprog -f"
+mvcmd="$mvprog"
+src=""
+dst=""
+dir_arg=""
+
+while [ x"$1" != x ]; do
+    case $1 in
+       -c) instcmd=$cpprog
+           shift
+           continue;;
+
+       -d) dir_arg=true
+           shift
+           continue;;
+
+       -m) chmodcmd="$chmodprog $2"
+           shift
+           shift
+           continue;;
+
+       -o) chowncmd="$chownprog $2"
+           shift
+           shift
+           continue;;
+
+       -g) chgrpcmd="$chgrpprog $2"
+           shift
+           shift
+           continue;;
+
+       -s) stripcmd=$stripprog
+           shift
+           continue;;
+
+       -t=*) transformarg=`echo $1 | sed 's/-t=//'`
+           shift
+           continue;;
+
+       -b=*) transformbasename=`echo $1 | sed 's/-b=//'`
+           shift
+           continue;;
+
+       *)  if [ x"$src" = x ]
+           then
+               src=$1
+           else
+               # this colon is to work around a 386BSD /bin/sh bug
+               :
+               dst=$1
+           fi
+           shift
+           continue;;
+    esac
+done
+
+if [ x"$src" = x ]
+then
+       echo "$0: no input file specified" >&2
+       exit 1
+else
+       :
+fi
+
+if [ x"$dir_arg" != x ]; then
+       dst=$src
+       src=""
+
+       if [ -d "$dst" ]; then
+               instcmd=:
+               chmodcmd=""
+       else
+               instcmd=$mkdirprog
+       fi
+else
+
+# Waiting for this to be detected by the "$instcmd $src $dsttmp" command
+# might cause directories to be created, which would be especially bad
+# if $src (and thus $dsttmp) contains '*'.
+
+       if [ -f "$src" ] || [ -d "$src" ]
+       then
+               :
+       else
+               echo "$0: $src does not exist" >&2
+               exit 1
+       fi
+
+       if [ x"$dst" = x ]
+       then
+               echo "$0: no destination specified" >&2
+               exit 1
+       else
+               :
+       fi
+
+# If destination is a directory, append the input filename; if your system
+# does not like double slashes in filenames, you may need to add some logic
+
+       if [ -d "$dst" ]
+       then
+               dst=$dst/`basename "$src"`
+       else
+               :
+       fi
+fi
+
+## this sed command emulates the dirname command
+dstdir=`echo "$dst" | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'`
+
+# Make sure that the destination directory exists.
+#  this part is taken from Noah Friedman's mkinstalldirs script
+
+# Skip lots of stat calls in the usual case.
+if [ ! -d "$dstdir" ]; then
+defaultIFS='
+       '
+IFS="${IFS-$defaultIFS}"
+
+oIFS=$IFS
+# Some sh's can't handle IFS=/ for some reason.
+IFS='%'
+set - `echo "$dstdir" | sed -e 's@/@%@g' -e 's@^%@/@'`
+IFS=$oIFS
+
+pathcomp=''
+
+while [ $# -ne 0 ] ; do
+       pathcomp=$pathcomp$1
+       shift
+
+       if [ ! -d "$pathcomp" ] ;
+        then
+               $mkdirprog "$pathcomp"
+       else
+               :
+       fi
+
+       pathcomp=$pathcomp/
+done
+fi
+
+if [ x"$dir_arg" != x ]
+then
+       $doit $instcmd "$dst" &&
+
+       if [ x"$chowncmd" != x ]; then $doit $chowncmd "$dst"; else : ; fi &&
+       if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd "$dst"; else : ; fi &&
+       if [ x"$stripcmd" != x ]; then $doit $stripcmd "$dst"; else : ; fi &&
+       if [ x"$chmodcmd" != x ]; then $doit $chmodcmd "$dst"; else : ; fi
+else
+
+# If we're going to rename the final executable, determine the name now.
+
+       if [ x"$transformarg" = x ]
+       then
+               dstfile=`basename "$dst"`
+       else
+               dstfile=`basename "$dst" $transformbasename |
+                       sed $transformarg`$transformbasename
+       fi
+
+# don't allow the sed command to completely eliminate the filename
+
+       if [ x"$dstfile" = x ]
+       then
+               dstfile=`basename "$dst"`
+       else
+               :
+       fi
+
+# Make a couple of temp file names in the proper directory.
+
+       dsttmp=$dstdir/_inst.$$_
+       rmtmp=$dstdir/_rm.$$_
+
+# Trap to clean up temp files at exit.
+
+       trap 'status=$?; rm -f "$dsttmp" "$rmtmp" && exit $status' 0
+       trap '(exit $?); exit' 1 2 13 15
+
+# Move or copy the file name to the temp name
+
+       $doit $instcmd "$src" "$dsttmp" &&
+
+# and set any options; do chmod last to preserve setuid bits
+
+# If any of these fail, we abort the whole thing.  If we want to
+# ignore errors from any of these, just make sure not to ignore
+# errors from the above "$doit $instcmd $src $dsttmp" command.
+
+       if [ x"$chowncmd" != x ]; then $doit $chowncmd "$dsttmp"; else :;fi &&
+       if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd "$dsttmp"; else :;fi &&
+       if [ x"$stripcmd" != x ]; then $doit $stripcmd "$dsttmp"; else :;fi &&
+       if [ x"$chmodcmd" != x ]; then $doit $chmodcmd "$dsttmp"; else :;fi &&
+
+# Now remove or move aside any old file at destination location.  We try this
+# two ways since rm can't unlink itself on some systems and the destination
+# file might be busy for other reasons.  In this case, the final cleanup
+# might fail but the new file should still install successfully.
+
+{
+       if [ -f "$dstdir/$dstfile" ]
+       then
+               $doit $rmcmd -f "$dstdir/$dstfile" 2>/dev/null ||
+               $doit $mvcmd -f "$dstdir/$dstfile" "$rmtmp" 2>/dev/null ||
+               {
+                 echo "$0: cannot unlink or rename $dstdir/$dstfile" >&2
+                 (exit 1); exit
+               }
+       else
+               :
+       fi
+} &&
+
+# Now rename the file to the real destination.
+
+       $doit $mvcmd "$dsttmp" "$dstdir/$dstfile"
+
+fi &&
+
+# The final little trick to "correctly" pass the exit status to the exit trap.
+
+{
+       (exit 0); exit
+}
diff --git a/key.anonymous b/key.anonymous
new file mode 100644 (file)
index 0000000..ebe05c1
--- /dev/null
@@ -0,0 +1,9 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIBPQIBAAJBANNQC7sHwu9B33OIvC+hGIRXXQX3YAC1fWVmhUo/UMIzLTnCwqGN
+ZPtrWPRfCoLA+ltf+cElXlq+DPAbHLdINpcCAwEAAQJBAIsDe9QctQcFROUSrQqA
+ZkqZ1p6YMNYGj2nn2gQQRyaZphkw92cJOfO2Fco35rcrSMFPVVEUmds6oZX6uDNP
+SyECIQDv/F5mDHnhopFynlO2yccMIQ5e7B25Qd728TopHubnmQIhAOFp3AP9uzBt
+Q9H6zoHRHoHugU4RBnzH5sO39NqSyi2vAiEA6fc7vPfK15ybeog7C0iawwtzLD28
+GMHMcynozvAuo3kCIQCT5dkt5TUEuSxegaktFABoUA0XI6SWCrMmh3RoVRfCkQIh
+AK1nC9pyoManFdtDXSL6fdpaLz7gIlJwkz5hgPW43bwe
+-----END RSA PRIVATE KEY-----
diff --git a/krell.c b/krell.c
new file mode 100644 (file)
index 0000000..3f43151
--- /dev/null
+++ b/krell.c
@@ -0,0 +1,416 @@
+/*
+ * Copyright (C) 2004-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.
+ */
+
+
+//#define PRINTF printf
+#define PRINTF(a, ...)
+
+#include <gkrellm2/gkrellm.h>
+#include <utime.h>
+#include <dirent.h>
+#include <errno.h>
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+
+static void create_para_ctrl(GtkWidget *, gint);
+static void update_para_ctrl(void);
+static void create_tab(GtkWidget *);
+static void apply_config(void);
+static void load_config(gchar *);
+static void save_config(FILE *);
+
+static GkrellmMonitor monitor = {
+        "Parakrell",            /* Name, for config tab. */
+        0,                      /* Id,  0 if a plugin */
+        create_para_ctrl,       /* The create function */
+        update_para_ctrl,       /* The update function */
+        create_tab,             /* The config tab create function */
+        apply_config,           /* Apply the config function */
+        save_config,            /* Save user config */
+        load_config,            /* Load user config */
+        "para_krell",           /* config keyword */
+        NULL,                   /* Undefined 2  */
+        NULL,                   /* Undefined 1  */
+        NULL,                   /* Undefined 0  */
+        MON_APM,                /* Insert plugin before this monitor */
+        NULL,                   /* Handle if a plugin, filled in by GKrellM */
+        NULL                    /* path if a plugin, filled in by GKrellM */
+};
+
+typedef struct {
+       GkrellmPiximage *image;
+       GkrellmDecalbutton *button;
+       gint x,y,w,h;
+       double x_scale,y_scale;
+} ControlButton;
+
+static ControlButton prev_button = {
+       .image = NULL,
+       .button = NULL,
+       .x = 10,
+       .y = 10,
+       .w = 50,
+       .h = 50,
+       .x_scale = 1,
+       .y_scale = 1,
+};
+
+GkrellmPiximage *piximage;
+GkrellmPanel *panel;
+GtkWidget *fileread_vbox;
+GdkPixbuf *pixbuf;
+gint song_change_input_id;
+FILE *song_change_fd;
+GIOChannel *song_change_channel;
+
+static struct timeval sc_open_time;
+
+static gchar *info_text =
+        "Parakrell displays an image corresponding to the soundfile\n"
+        "currently played by paraslash.\n\n"
+
+        "The plugin's panel is divided in 9 small squares, like      \n"
+        "the numpad on a PC keyboard.  For each square there is an   \n"
+        "associated button and a number between 1 and 9.\n           \n"
+
+        "Each button is bound to three different commands for left,  \n"
+        "middle and right mouse button.  Middle and right button are \n"
+        "reserved for setting volume and jumping around in the song, \n"
+        "respectively. Left button works as follows:\n               \n"
+
+       "7  8  9                       7: slider, 8: sdl_gui, 9: next \n"
+       "4  5  6                       4: ps,     5: play,    6: dbadm \n"
+       "1  2  3                       1: stop,   2: gui,     3: pause\n"
+
+       "\n\nAuthor:\n"
+       "Andre Noll <maan@systemlinux.org>\n"
+       "Copyright (C) 2004-2005\n"
+       "Distributed under the GNU General Public License.\n";
+
+#define MAXLINE 255
+
+static gboolean launch_cmd(char *cmd)
+{
+
+       gchar **argv;
+       GError *err = NULL;
+       gboolean res;
+
+       PRINTF("%s: \n", __func__);
+        if (!cmd || *cmd == '\0')
+               return -1;
+
+        g_shell_parse_argv(cmd, NULL, &argv, NULL);
+       res = g_spawn_async(
+               NULL,
+               argv,
+               NULL,
+               G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL,
+               NULL,
+               NULL,
+               NULL,
+               &err
+       );
+        if (!res && err) {
+               gkrellm_message_dialog(NULL, err->message);
+               g_error_free(err);
+       }
+        g_strfreev(argv);
+       return res;
+}
+
+static gboolean cb_in_button(GkrellmDecalbutton *b,
+       GdkEventButton *ev, ControlButton *cbut)
+{
+       gint x, y, area, width = gkrellm_chart_width();
+       x = (int) (3 * ev->x / width);
+       y = (int) (3 - 3 * ev->y / width);
+       area = 3 * y + x + 1;
+       char buf[MAXLINE];
+
+       PRINTF("%s: button %i pressed on area %i\n", __func__,
+               ev->button, area);
+       if (ev->button == 1) {
+               switch (area) {
+               case 1:
+                       launch_cmd("para_client stop");
+                       return 0;
+               case 2:
+                       launch_cmd("xterm -e para_gui -a");
+                       return 0;
+               case 3:
+                       launch_cmd("para_client pause");
+                       return 0;
+               case 4:
+                       launch_cmd("para_client ps");
+                       return 0;
+               case 5:
+                       launch_cmd("para_client play");
+                       return 0;
+               case 6:
+                       launch_cmd("xterm -e para_dbadm");
+                       return 0;
+               case 7:
+                       launch_cmd("para_slider");
+                       return 0;
+               case 8:
+                       launch_cmd("para_sdl_gui -f");
+                       return 0;
+               case 9:
+                       launch_cmd("para_client next");
+                       return 0;
+               }
+               return 0;
+       }
+       sprintf(buf, "%s %i", ev->button == 2? "aumix -v" : "para_client jmp",
+               area * 10);
+       return launch_cmd(buf);
+}
+
+static void make_button(ControlButton *cbut, gint fn_id)
+{
+       PRINTF("%s: gkrellm_make_scaled_button\n", __func__);
+        cbut->button = gkrellm_make_scaled_button(
+               panel,
+               cbut->image,
+                NULL,
+               GINT_TO_POINTER(fn_id),
+               FALSE,
+               FALSE,
+               2,
+               0,
+               1,
+                cbut->x,
+               cbut->y,
+               cbut->w,
+               cbut->h
+       );
+       PRINTF("%s: making botton\n", __func__);
+        gkrellm_set_in_button_callback(cbut->button, cb_in_button, cbut);
+}
+
+
+static void load_img(void)
+{
+       gint width = gkrellm_chart_width();
+       gint out = 0, ret;
+       FILE *pipe = NULL;
+       char buf[MAXLINE];
+       size_t num_read = 0;
+       gchar *filename = gkrellm_make_data_file_name("para", "pic.jpg");
+
+       PRINTF("%s: Opening %s\n", __func__, filename);
+       if ((out = creat(filename, S_IRUSR | S_IWUSR)) < 0) {
+               perror("open");
+               goto out;
+       }
+       pipe = popen("para_client pic", "r");
+       if (!pipe)
+               goto out;
+       while ((ret = read(fileno(pipe), buf, sizeof(buf) - 1)) > 0) {
+               if (write(out, buf, ret) < 0) {
+                       perror("Write");
+                       goto out;
+               }
+               num_read += ret;
+       }
+       if (ret < 0) {
+               PRINTF("%s: Read Error\n", __func__);
+               goto out;
+       }
+       PRINTF("%s: new pic created (%i bytes)\n", __func__, num_read);
+       if (num_read < 500)
+               goto out;
+
+       if (piximage) {
+               gkrellm_destroy_piximage(piximage);
+               g_free(pixbuf);
+       }
+       PRINTF("%s: creating new piximage\n", __func__);
+       piximage = gkrellm_piximage_new_from_file(filename);
+       if (!piximage) {
+               PRINTF("%s: can not load image\n", __func__);
+               goto out;
+       }
+       pixbuf = gkrellm_scale_piximage_to_pixbuf(piximage, width, width);
+out:
+       g_free(filename);
+       if (pipe)
+               pclose(pipe);
+       if (out)
+               close(out);
+}
+
+static void create_tab(GtkWidget *tab_vbox)
+{
+       GtkTextBuffer *textbuf;
+       GtkWidget *text;
+       GtkWidget *scrolled, *vbox = tab_vbox;
+
+       PRINTF("%s: \n", __func__);
+
+       scrolled = gtk_scrolled_window_new(NULL, NULL);
+       gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
+                                      GTK_POLICY_AUTOMATIC,
+                                      GTK_POLICY_AUTOMATIC);
+       textbuf = gtk_text_buffer_new(NULL);
+       gtk_text_buffer_set_text(textbuf, info_text, -1);
+       text = gtk_text_view_new_with_buffer(textbuf);
+       gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
+       gtk_container_add(GTK_CONTAINER(scrolled), text);
+       gtk_box_pack_start(GTK_BOX(vbox), scrolled, TRUE, TRUE, 0);
+}
+
+static gboolean song_change(GIOChannel *channel, GIOCondition condition,
+               gpointer panel)
+{
+       char *str_return;
+       gsize length;
+
+       PRINTF("%s: input condition %i "
+               "(song_change channel fd = %i)\n", __func__, condition,
+               g_io_channel_unix_get_fd(song_change_channel));
+       if (!song_change_fd) {
+               PRINTF("%s: no song_change_fd\n", __func__);
+               goto err_out;
+       }
+       if (!(condition & (G_IO_IN | G_IO_PRI))) {
+               PRINTF("%s: song change pipe died\n", __func__);
+               song_change_fd = NULL;
+               wait(NULL);
+               return TRUE;
+               goto err_out;
+       }
+       if (!channel->is_readable) {
+               PRINTF("%s: fd not readable\n", __func__);
+               goto err_out;
+       }
+       PRINTF("%s: reading data\n", __func__);
+       if (g_io_channel_read_line(channel, &str_return,
+                       &length, NULL, NULL) == G_IO_STATUS_NORMAL) {
+               PRINTF("%s: next song: %s", __func__, str_return);
+               g_free(str_return);
+               if (channel != song_change_channel)
+                       goto err_out;
+               load_img();
+               return TRUE;
+       }
+err_out:
+       g_io_channel_unref(channel);
+       g_io_channel_shutdown(channel, TRUE, NULL);
+       return FALSE;
+}
+
+static void create_song_change(void)
+{
+       if (song_change_fd)
+               return;
+       gettimeofday(&sc_open_time, NULL);
+       PRINTF("%s: para_client sc\n", __func__);
+       song_change_fd = popen("para_client sc", "r");
+       if (!song_change_fd) {
+               PRINTF("%s: para_client sc failed\n", __func__);
+               return;
+       }
+       song_change_channel = g_io_channel_unix_new(fileno(song_change_fd));
+       g_io_channel_set_close_on_unref(song_change_channel, TRUE);
+       g_io_add_watch(song_change_channel,
+               G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+               &song_change, panel);
+}
+
+static void update_para_ctrl(void)
+{
+       GdkDrawable  *drawable = panel->drawing_area->window;
+       gint width = gkrellm_chart_width();
+
+       if (!drawable)
+               PRINTF("%s: No drawable\n", __func__);
+       else
+               gkrellm_paste_pixbuf(pixbuf, drawable, 0, 0, width, width);
+       if (!song_change_fd) {
+               struct timeval now;
+               PRINTF("%s: no song_change_fd\n", __func__);
+
+               gettimeofday(&now, NULL);
+               if (now.tv_sec > sc_open_time.tv_sec + 5)
+                       create_song_change();
+       }
+}
+
+static void destroy_panel(void)
+{
+       PRINTF("%s: \n", __func__);
+       if (!panel) 
+               return;
+       PRINTF("%s: destroying panel\n", __func__);
+       gkrellm_panel_destroy(panel);
+       panel = NULL;
+}
+
+#if 0
+static void destroy_song_change(void)
+{
+       PRINTF("%s: \n", __func__);
+       if (!song_change_fd)
+               return;
+}
+#endif
+static void destroy_all(void)
+{
+//     destroy_song_change();
+       destroy_panel();
+}
+
+static void create_para_ctrl(GtkWidget *vbox, gint first_create)
+{
+       gint style_id = gkrellm_lookup_meter_style_id(UPTIME_STYLE_NAME);
+       GkrellmStyle *style = gkrellm_meter_style(style_id);
+       gint width = gkrellm_chart_width();
+
+       destroy_all();
+       create_song_change();
+       panel = gkrellm_panel_new0();
+       gkrellm_panel_configure(panel, NULL, style);
+       gkrellm_panel_configure_set_height(panel, width);
+       PRINTF("%s: creating panel\n", __func__);
+       gkrellm_panel_create(vbox, &monitor, panel);
+       make_button(&prev_button, 1);
+}
+
+static void apply_config(void)
+{
+       PRINTF("%s: \n", __func__);
+}
+
+static void load_config(gchar * arg)
+{
+        PRINTF("%s: \n", __func__);
+
+}
+
+static void save_config(FILE * f)
+{
+        PRINTF("%s: \n", __func__);
+}
+
+GkrellmMonitor *gkrellm_init_plugin(void)
+{
+       return &monitor;
+}
diff --git a/list.h b/list.h
new file mode 100644 (file)
index 0000000..0080c80
--- /dev/null
+++ b/list.h
@@ -0,0 +1,172 @@
+/*
+ * Copied from the Linux kernel source tree, version 2.6.13.
+ *
+ * Licensed under the GPL v2 as per the whole kernel source tree.
+ *
+ */
+
+#include <stddef.h> /* offsetof */
+#ifndef _LINUX_LIST_H
+#define _LINUX_LIST_H
+
+#define container_of(ptr, type, member) ({                      \
+       const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
+       (type *)( (char *)__mptr - offsetof(type,member) );})
+
+/*
+ * These are non-NULL pointers that will result in page faults
+ * under normal circumstances, used to verify that nobody uses
+ * non-initialized list entries.
+ */
+#define LIST_POISON1  ((void *) 0x00100100)
+#define LIST_POISON2  ((void *) 0x00200200)
+
+/*
+ * Simple doubly linked list implementation.
+ *
+ * Some of the internal functions ("__xxx") are useful when
+ * manipulating whole lists rather than single entries, as
+ * sometimes we already know the next/prev entries and we can
+ * generate better code by using them directly rather than
+ * using the generic single-entry routines.
+ */
+
+struct list_head {
+       struct list_head *next, *prev;
+};
+
+#define INIT_LIST_HEAD(ptr) do { \
+       (ptr)->next = (ptr); (ptr)->prev = (ptr); \
+} while (0)
+
+/*
+ * Insert a new entry between two known consecutive entries.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_add(struct list_head *new,
+                             struct list_head *prev,
+                             struct list_head *next)
+{
+       next->prev = new;
+       new->next = next;
+       new->prev = prev;
+       prev->next = new;
+}
+
+/**
+ * list_add - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it after
+ *
+ * Insert a new entry after the specified head.
+ * This is good for implementing stacks.
+ */
+static inline void list_add(struct list_head *new, struct list_head *head)
+{
+       __list_add(new, head, head->next);
+}
+
+/**
+ * list_add_tail - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it before
+ *
+ * Insert a new entry before the specified head.
+ * This is useful for implementing queues.
+ */
+static inline void list_add_tail(struct list_head *new, struct list_head *head)
+{
+       __list_add(new, head->prev, head);
+}
+
+/*
+ * Delete a list entry by making the prev/next entries
+ * point to each other.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_del(struct list_head * prev, struct list_head * next)
+{
+       next->prev = prev;
+       prev->next = next;
+}
+
+/**
+ * list_del - deletes entry from list.
+ * @entry: the element to delete from the list.
+ * Note: list_empty on entry does not return true after this, the entry is
+ * in an undefined state.
+ */
+static inline void list_del(struct list_head *entry)
+{
+       __list_del(entry->prev, entry->next);
+       entry->next = LIST_POISON1;
+       entry->prev = LIST_POISON2;
+}
+
+/**
+ * list_move - delete from one list and add as another's head
+ * @list: the entry to move
+ * @head: the head that will precede our entry
+ */
+static inline void list_move(struct list_head *list, struct list_head *head)
+{
+        __list_del(list->prev, list->next);
+        list_add(list, head);
+}
+
+/**
+ * list_empty - tests whether a list is empty
+ * @head: the list to test.
+ */
+static inline int list_empty(const struct list_head *head)
+{
+       return head->next == head;
+}
+
+/**
+ * list_entry - get the struct for this entry
+ * @ptr:       the &struct list_head pointer.
+ * @type:      the type of the struct this is embedded in.
+ * @member:    the name of the list_struct within the struct.
+ */
+#define list_entry(ptr, type, member) \
+       container_of(ptr, type, member)
+
+/**
+ * list_for_each_safe  -       iterate over a list safe against removal of list entry
+ * @pos:       the &struct list_head to use as a loop counter.
+ * @n:         another &struct list_head to use as temporary storage
+ * @head:      the head for your list.
+ */
+#define list_for_each_safe(pos, n, head) \
+       for (pos = (head)->next, n = pos->next; pos != (head); \
+               pos = n, n = pos->next)
+
+/**
+ * list_for_each_entry -       iterate over list of given type
+ * @pos:       the type * to use as a loop counter.
+ * @head:      the head for your list.
+ * @member:    the name of the list_struct within the struct.
+ */
+#define list_for_each_entry(pos, head, member)                         \
+       for (pos = list_entry((head)->next, typeof(*pos), member);      \
+            &pos->member != (head);    \
+            pos = list_entry(pos->member.next, typeof(*pos), member))
+
+/**
+ * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
+ * @pos:       the type * to use as a loop counter.
+ * @n:         another type * to use as temporary storage
+ * @head:      the head for your list.
+ * @member:    the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_safe(pos, n, head, member)                 \
+       for (pos = list_entry((head)->next, typeof(*pos), member),      \
+               n = list_entry(pos->member.next, typeof(*pos), member); \
+            &pos->member != (head);                                    \
+            pos = n, n = list_entry(n->member.next, typeof(*n), member))
+#endif /* _LIST_H */
diff --git a/mp3.c b/mp3.c
new file mode 100644 (file)
index 0000000..c2d0f63
--- /dev/null
+++ b/mp3.c
@@ -0,0 +1,504 @@
+/*
+ * Copyright (C) 2003-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 mp3.c para_server's mp3 audio format handler */
+
+/*
+ * This file is based in part on mp3tech.c and mp3tech.h, Copyright (C)
+ * 2000-2001 Cedric Tefft <cedric@earthling.net>, which in turn is based
+ * in part on
+ *
+ *     * MP3Info 0.5 by Ricardo Cerqueira <rmc@rccn.net>
+ *     * MP3Stat 0.9 by Ed Sweetman <safemode@voicenet.com> and
+ *                      Johannes Overmann <overmann@iname.com>
+ */
+
+#include "server.cmdline.h"
+#include "server.h"
+#include "afs.h"
+#include "gcc-compat.h"
+#include "error.h"
+
+/** \cond some defines and structs which are only used in this file */
+
+/*
+ * MIN_CONSEC_GOOD_FRAMES defines how many consecutive valid MP3 frames we need
+ * to see before we decide we are looking at a real MP3 file
+ */
+#define MIN_CONSEC_GOOD_FRAMES 3
+
+#define FRAME_HEADER_SIZE 4
+#define MIN_FRAME_SIZE 21
+
+struct mp3header {
+       unsigned long sync;
+       unsigned int version;
+       unsigned int layer;
+       unsigned int crc;
+       unsigned int bitrate;
+       unsigned int freq;
+       unsigned int padding;
+       unsigned int mode;
+       unsigned int copyright;
+       unsigned int original;
+       unsigned int emphasis;
+};
+
+struct id3tag {
+       char title[31];
+       char artist[31];
+       char album[31];
+       char year[5];
+       char comment[31];
+};
+
+struct mp3info {
+       char *filename;
+       FILE *file;
+       struct mp3header header;
+       int id3_isvalid;
+       struct id3tag id3;
+       int vbr;
+       long unsigned br_average;
+       long unsigned seconds;
+       int frames;
+       int freq;
+};
+
+/** \endcond */
+static int frequencies[3][4] = {
+       {22050,24000,16000,50000}, /* MPEG 2.0 */
+       {44100,48000,32000,50000}, /* MPEG 1.0 */
+       {11025,12000,8000,50000} /* MPEG 2.5 */
+};
+
+static int mp3info_bitrate[2][3][14] = {
+{ /* MPEG 2.0 */
+       {32,48,56,64,80,96,112,128,144,160,176,192,224,256}, /* layer 1 */
+       {8,16,24,32,40,48,56,64,80,96,112,128,144,160}, /* layer 2 */
+       {8,16,24,32,40,48,56,64,80,96,112,128,144,160} /* layer 3 */
+},
+
+{ /* MPEG 1.0 */
+       {32,64,96,128,160,192,224,256,288,320,352,384,416,448}, /* layer 1 */
+       {32,48,56,64,80,96,112,128,160,192,224,256,320,384}, /* layer 2 */
+       {32,40,48,56,64,80,96,112,128,160,192,224,256,320} /* layer 3 */
+}
+};
+
+static int frame_size_index[] = {24000, 72000, 72000};
+static char *mode_text[] = {"stereo", "joint stereo", "dual channel", "mono", "invalid"};
+
+static struct mp3info mp3;
+static char mp3buf[8192];
+static int chunk_size;
+static struct audio_format *af;
+
+static __must_check int para_fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
+{
+       size_t res = fread(ptr, size, nmemb, stream);
+       if (res == nmemb)
+               return size * nmemb;
+       if (feof(stream))
+               return 0;
+       return -E_FREAD;
+}
+
+static int header_frequency(struct mp3header *h)
+{
+       if (h->version > 2 || h->freq > 3)
+               return -E_HEADER_FREQ;
+       return frequencies[h->version][h->freq];
+}
+
+static char *header_mode(struct mp3header *h)
+{
+       if (h->mode > 4)
+               h->mode = 4; /* invalid */
+       return mode_text[h->mode];
+}
+static int header_bitrate(struct mp3header *h)
+{
+       if (h->layer > 3 || h->bitrate > 14)
+               return -E_HEADER_BITRATE;
+       return mp3info_bitrate[h->version & 1][3 - h->layer][h->bitrate - 1];
+}
+
+static int frame_length(struct mp3header *header)
+{
+       int hb, hf = header_frequency(header);
+
+       if (hf < 0)
+               return hf;
+       hb = header_bitrate(header);
+       if (hb < 0)
+               return hb;
+       if (header->sync != 0xFFE || header->layer > 3)
+               return -E_FRAME;
+       return frame_size_index[3 - header->layer] *
+               ((header->version & 1) + 1) * hb / hf
+               + header->padding;
+}
+
+static void write_info_str(char *info_str)
+{
+       int v = mp3.id3_isvalid;
+
+       snprintf(info_str, MMD_INFO_SIZE,
+               "audio_file_info1:%d x %lu, %lu kbit/s (%cbr) %i KHz %s\n"
+               "audio_file_info2:%s, by %s\n"
+               "audio_file_info3:A: %s, Y: %s, C: %s\n",
+               mp3.frames,
+               af->chunk_tv.tv_usec,
+               mp3.br_average,
+               mp3.vbr? 'v' : 'c',
+               mp3.freq / 1000,
+               header_mode(&mp3.header),
+               v && *mp3.id3.title? mp3.id3.title : "(title tag not set)",
+               v && *mp3.id3.artist? mp3.id3.artist : "(artist tag not set)",
+               v && *mp3.id3.album? mp3.id3.album : "(album tag not set)",
+               v && *mp3.id3.year? mp3.id3.year : "????",
+               v && *mp3.id3.comment? mp3.id3.comment : "(comment tag not set)"
+       );
+}
+
+/*
+ * Remove trailing whitespace from the end of a string
+ */
+static char *unpad(char *string)
+{
+       char *pos = string + strlen(string) - 1;
+       while (isspace(pos[0]))
+               (pos--)[0] = 0;
+       return string;
+}
+
+static int compare_headers(struct mp3header *h1,struct mp3header *h2)
+{
+       if ((*(uint*)h1) == (*(uint*)h2))
+               return 1;
+       if ((h1->version == h2->version) &&
+                       (h1->layer == h2->layer) &&
+                       (h1->crc == h2->crc) &&
+                       (h1->freq == h2->freq) &&
+                       (h1->mode == h2->mode) &&
+                       (h1->copyright == h2->copyright) &&
+                       (h1->original == h2->original) &&
+                       (h1->emphasis == h2->emphasis))
+               return 1;
+       else
+               return 0;
+}
+
+/**
+ * get next MP3 frame header.
+ *
+ * \param stream to read the header from
+ * \param header structure that gets filled in by get_header()
+ *
+ * \return On success, the header frame length is returned.  A return value of
+ * zero means that we did not retrieve a valid frame header, and a negative
+ * return value indicates an error.
+ */
+static int get_header(FILE *file, struct mp3header *header)
+{
+       unsigned char buffer[FRAME_HEADER_SIZE];
+       int fl, ret;
+
+       if (!file || !header)
+               return -E_MP3_NO_FILE;
+       ret = para_fread(buffer, FRAME_HEADER_SIZE, 1, file);
+       if (ret < FRAME_HEADER_SIZE) {
+               header->sync = 0;
+               return ret < 0? ret : 0;
+       }
+       header->layer = (buffer[1] >> 1) & 3;
+       header->sync = (((int)buffer[0]<<4) | ((int)(buffer[1]&0xE0)>>4));
+       if (buffer[1] & 0x10)
+               header->version = (buffer[1] >> 3) & 1;
+       else
+               header->version = 2;
+       if ((header->sync != 0xFFE) || (header->layer != 1)) {
+               header->sync = 0;
+//             PARA_DEBUG_LOG("%s: header not found\n", __func__);
+               return 0;
+       }
+       header->crc = buffer[1] & 1;
+       header->bitrate = (buffer[2] >> 4) & 0x0F;
+//     PARA_DEBUG_LOG("%s: found header, bitrate: %u\n", __func__,
+//             header->bitrate);
+       header->freq = (buffer[2] >> 2) & 0x3;
+       header->padding = (buffer[2] >>1) & 0x1;
+       header->mode = (buffer[3] >> 6) & 0x3;
+       fl = frame_length(header);
+       return (fl >= MIN_FRAME_SIZE)? fl : -E_FRAME_LENGTH;
+}
+
+/**
+ * find the next mp3 header
+ *
+ * \return On success, the length of the next frame header. If the end of the
+ * file was reached, the function returns zero. On errors, a negative value is
+ * returned.
+ *
+ */
+static int mp3_seek_next_header(void)
+{
+       int k, l = 0, c, first_len;
+       struct mp3header h, h2;
+       long valid_start = 0;
+
+       while (1) {
+               while ((c = fgetc(mp3.file)) != 255 && (c != EOF))
+                       ; /* nothing */
+               if (c != 255)
+                       return 0;
+               ungetc(c, mp3.file);
+               valid_start = ftell(mp3.file);
+               first_len = get_header(mp3.file, &h);
+               if (first_len <= 0)
+                       continue;
+               if (fseek(mp3.file, first_len - FRAME_HEADER_SIZE, SEEK_CUR) < 0)
+                       return -E_FSEEK;
+               for (k = 1; k < MIN_CONSEC_GOOD_FRAMES; k++) {
+                       if ((l = get_header(mp3.file, &h2)) <= 0)
+                               break;
+                       if (!compare_headers(&h, &h2))
+                               break;
+                       fseek(mp3.file, l - FRAME_HEADER_SIZE, SEEK_CUR);
+               }
+               if (k == MIN_CONSEC_GOOD_FRAMES) {
+                       fseek(mp3.file, valid_start, SEEK_SET);
+                       memcpy(&(mp3.header), &h2, sizeof(struct mp3header));
+                       return first_len;
+               }
+       }
+}
+
+static int mp3_get_id3(void)
+{
+       char fbuf[4];
+
+       mp3.id3_isvalid = 0;
+       mp3.id3.title[0] = '\0';
+       mp3.id3.artist[0] = '\0';
+       mp3.id3.album[0] = '\0';
+       mp3.id3.comment[0] = '\0';
+       mp3.id3.year[0] = '\0';
+       if (fseek(mp3.file, -128, SEEK_END))
+               return -E_FSEEK;
+       if (para_fread(fbuf, 1, 3, mp3.file) < 0)
+               return -E_FREAD;
+       fbuf[3] = '\0';
+       if (strcmp("TAG", fbuf)) {
+               PARA_INFO_LOG("%s", "no id3 tag\n");
+               return 0;
+       }
+       if (fseek(mp3.file, -125, SEEK_END) < 0)
+               return -E_FSEEK;
+       if (para_fread(mp3.id3.title, 1, 30, mp3.file) != 30)
+               return -E_FREAD;
+       mp3.id3.title[30] = '\0';
+       if (para_fread(mp3.id3.artist, 1, 30, mp3.file) != 30)
+               return -E_FREAD;
+       mp3.id3.artist[30] = '\0';
+       if (para_fread(mp3.id3.album, 1, 30, mp3.file) != 30)
+               return -E_FREAD;
+       mp3.id3.album[30] = '\0';
+       if (para_fread(mp3.id3.year, 1, 4, mp3.file) != 4)
+               return -E_FREAD;
+       mp3.id3.year[4] = '\0';
+       if (para_fread(mp3.id3.comment, 1, 30, mp3.file) != 30)
+               return -E_FREAD;
+       mp3.id3.comment[30] = '\0';
+       mp3.id3_isvalid = 1;
+       unpad(mp3.id3.title);
+       unpad(mp3.id3.artist);
+       unpad(mp3.id3.album);
+       unpad(mp3.id3.year);
+       unpad(mp3.id3.comment);
+       return 1;
+}
+
+static int find_valid_start(void)
+{
+       int frame_len;
+
+       if (!mp3.file)
+               return -E_MP3_NO_FILE;
+       frame_len = get_header(mp3.file, &mp3.header);
+       if (frame_len < 0)
+               return frame_len;
+       if (!frame_len) {
+               frame_len = mp3_seek_next_header();
+               if (frame_len <= 0)
+                       return frame_len;
+       } else
+               if (fseek(mp3.file, -FRAME_HEADER_SIZE, SEEK_CUR) < 0)
+                       return -E_FSEEK;
+       if (frame_len <= 1) /* FRAME_HEADER_SIZE? */
+               return -E_FRAME_LENGTH;
+       return frame_len;
+}
+
+static int mp3_read_info(void)
+{
+       long fl_avg = 0, freq_avg = 0, br_avg = 0, fcount = 0;
+       int ret, len = 0, old_br = -1;
+       struct timeval total_time = {0, 0};
+
+       ret = mp3_get_id3();
+       if (ret < 0)
+               return ret;
+       rewind(mp3.file);
+       mp3.vbr = 0;
+       mp3.freq = 0;
+       while (1) {
+               int freq, br, fl;
+               struct timeval tmp, cct; /* current chunk time */
+               if (len > 0)
+                       if (fseek(mp3.file, len, SEEK_CUR) < 0)
+                               return -E_FSEEK;
+               len = find_valid_start();
+               if (len <= 0)
+                       break;
+               freq = header_frequency(&mp3.header);
+               br = header_bitrate(&mp3.header);
+               fl = frame_length(&mp3.header);
+               if (freq < 0 || br < 0 || fl < 0)
+                       continue;
+               tmp.tv_sec = fl;
+               tmp.tv_usec = 0;
+               tv_divide(br * 125, &tmp, &cct);
+               tv_add(&cct, &total_time, &tmp);
+               total_time = tmp;
+//             PARA_DEBUG_LOG("%s: br: %d, freq: %d, fl: %d, cct: %lu\n", __func__, br, freq, fl, cct.tv_usec);
+               fcount++;
+               if (fcount == 1) {
+                       freq_avg = freq;
+                       br_avg = br;
+                       old_br = br;
+                       fl_avg = fl;
+                       continue;
+               }
+               freq_avg += (freq - freq_avg) / (fcount + 1);
+               fl_avg += (fl - fl_avg) / (fcount + 1);
+               br_avg += (br - br_avg) / (fcount + 1);
+               if (old_br != br)
+                       mp3.vbr = 1;
+               old_br = br;
+       }
+       if (!fcount || !freq_avg || !br_avg)
+               return -E_MP3_INFO;
+       mp3.br_average = br_avg;
+       mp3.freq = freq_avg;
+       mp3.frames = fcount;
+       mp3.seconds = (tv2ms(&total_time) + 500) / 1000;
+       tv_divide(fcount, &total_time, &af->chunk_tv);
+       rewind(mp3.file);
+       PARA_DEBUG_LOG("chunk_time: %lu\n", af->chunk_tv.tv_usec);
+       tv_scale(10, &af->chunk_tv, &af->eof_tv);
+       return 1;
+}
+
+/*
+ * Read mp3 information from audio file
+ */
+static int mp3_get_file_info(FILE *audio_file, char *info_str,
+       long unsigned *frames, int *seconds)
+{
+       int ret;
+
+       if (!audio_file)
+               return -E_MP3_NO_FILE;
+       mp3.file = audio_file;
+       ret = mp3_read_info();
+       if (ret < 0) {
+               mp3.file = NULL;
+               return ret;
+       }
+       write_info_str(info_str);
+       *frames = mp3.frames;
+       *seconds = mp3.seconds;
+       if (*seconds < 2 || !*frames)
+               return -E_MP3_INFO;
+       return 1;
+}
+
+static int mp3_reposition_stream(long unsigned new_frame)
+{
+       int count = 0, len;
+
+       PARA_DEBUG_LOG("jmp to frame %lu/%i\n", new_frame, mp3.frames);
+       rewind(mp3.file);
+       while (count < new_frame && (len = find_valid_start()) > 0) {
+//             PARA_DEBUG_LOG("%s: jmp to frame %d\n", __func__, count);
+               if (fseek(mp3.file, len, SEEK_CUR) < 0)
+                       return -E_FSEEK;
+               count++;
+       }
+       if (count != new_frame) {
+               rewind(mp3.file);
+               return -E_MP3_REPOS;
+       }
+       return 1;
+}
+
+static int mp3_read_next_chunk(void)
+{
+       int len = find_valid_start();
+
+       if (len <= 0) {
+               if (len < 0)
+                       PARA_ERROR_LOG("invalid frame len (%d)\n", len);
+               return len;
+       }
+       chunk_size = para_fread(mp3buf, len, 1, mp3.file);
+       if (len != chunk_size)
+               PARA_DEBUG_LOG("short read (%d/%d)\n", chunk_size, len);
+       return chunk_size;
+}
+
+static char *mp3_read_chunk(__unused long unsigned chunk_num, ssize_t *len)
+{
+       *len = mp3_read_next_chunk();
+       if (*len <= 0)
+               return NULL;
+       return mp3buf;
+}
+
+static void mp3_close_audio_file(void)
+{
+       if (!mp3.file)
+               return;
+       fclose(mp3.file);
+       mp3.file = NULL;
+}
+
+void mp3_init(void *p)
+{
+       af = p;
+       af->get_file_info = mp3_get_file_info;
+       af->reposition_stream = mp3_reposition_stream;
+       af->read_chunk = mp3_read_chunk;
+       af->close_audio_file = mp3_close_audio_file;
+       af->get_header_info = NULL;
+       /* eof_tv gets overwritten in mp3_get_file_info() */
+       af->eof_tv.tv_sec = 0;
+       af->eof_tv.tv_usec = 100 * 1000;
+}
diff --git a/mp3dec.c b/mp3dec.c
new file mode 100644 (file)
index 0000000..0100d04
--- /dev/null
+++ b/mp3dec.c
@@ -0,0 +1,137 @@
+/*
+ * 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 mp3dec.c paraslash's mp3 decoder */
+
+#include "gcc-compat.h"
+#include "para.h"
+
+#include "list.h"
+#include "filter.h"
+#include "error.h"
+#include <mad.h>
+#include "string.h"
+
+/** the output buffer size */
+#define MP3_OUTBUF_SIZE 128 * 1024
+
+/** \cond a helper macro */
+#define MAD_TO_SHORT(f) (f) >= MAD_F_ONE?  SHRT_MAX :\
+       (f) <= -MAD_F_ONE? -SHRT_MAX : (signed short) ((f) >> (MAD_F_FRACBITS - 15))
+/** \endcond */
+
+/**
+ * data specific to the mp3dec filter
+ *
+ * \sa filter, filter_node
+ */
+struct private_mp3dec_data {
+       /** information on the current mp3 stream */
+       struct mad_stream stream;
+       /** information about the frame which is currently decoded */
+       struct mad_frame frame;
+       /** contains the PCM output */
+       struct mad_synth synth;
+};
+
+static ssize_t mp3dec(char *inbuffer, size_t len, struct filter_node *fn)
+{
+       int i, ret;
+       struct private_mp3dec_data *pmd = fn->private_data;
+       size_t copy = MIN(len, 4096);
+
+       if (fn->loaded > fn->bufsize * 4 / 5)
+               return 0;
+       mad_stream_buffer(&pmd->stream, (unsigned char *) inbuffer, copy);
+       pmd->stream.error = 0;
+next_frame:
+       ret = mad_frame_decode(&pmd->frame, &pmd->stream);
+       if (ret) {
+               if (MAD_RECOVERABLE(pmd->stream.error) || pmd->stream.error == MAD_ERROR_BUFLEN)
+                       goto out;
+               PARA_ERROR_LOG("fatal: ret = %d, loaded = %d\n", ret, fn->loaded);
+               return -E_MAD_FRAME_DECODE;
+       }
+       mad_synth_frame(&pmd->synth, &pmd->frame);
+       fn->fci->samplerate = pmd->frame.header.samplerate;
+       fn->fci->channels = MAD_NCHANNELS(&pmd->frame.header);
+
+       for (i = 0; i < pmd->synth.pcm.length; i++) {
+               /* output format: unsigned 16 bit little endian */
+               signed short s = MAD_TO_SHORT(pmd->synth.pcm.samples[0][i]);
+               fn->buf[fn->loaded++] = s & 0xff;
+               fn->buf[fn->loaded++] = s >> 8;
+               if (MAD_NCHANNELS(&pmd->frame.header) == 2) { /* stereo */
+                       s = MAD_TO_SHORT(pmd->synth.pcm.samples[1][i]);
+                       fn->buf[fn->loaded++] = s & 0xff;
+                       fn->buf[fn->loaded++] = s >> 8;
+               }
+               if (fn->loaded != fn->bufsize) /* output buffer not full */
+                       continue;
+               PARA_ERROR_LOG("output buffer full: %d\n", fn->loaded);
+                       return -E_MP3DEC_OVERRUN;
+       }
+       if (fn->loaded <= fn->bufsize * 4 / 5)
+               goto next_frame;
+out:
+       if (pmd->stream.next_frame) { /* we still have some data */
+               size_t off = pmd->stream.bufend - pmd->stream.next_frame;
+               PARA_DEBUG_LOG("converted %d, %d input bytes, %d output bytes\n",
+                       len - off, off, fn->loaded);
+               return copy - off;
+       }
+       return copy;
+}
+
+static void mp3dec_close(struct filter_node *fn)
+{
+       struct private_mp3dec_data *pmd = fn->private_data;
+
+       mad_synth_finish(&pmd->synth);
+       mad_frame_finish(&pmd->frame);
+       mad_stream_finish(&pmd->stream);
+
+       free(fn->buf);
+       fn->buf = NULL;
+       free(pmd);
+       fn->private_data = NULL;
+}
+
+static void mp3dec_open(struct filter_node *fn)
+{
+       fn->private_data = para_calloc(sizeof(struct private_mp3dec_data));
+       struct private_mp3dec_data *pmd = fn->private_data;
+
+       mad_stream_init(&pmd->stream);
+       mad_frame_init(&pmd->frame);
+       mad_synth_init(&pmd->synth);
+       fn->loaded = 0;
+       fn->bufsize = MP3_OUTBUF_SIZE;
+       fn->buf = para_calloc(fn->bufsize);
+}
+/**
+ * the init function of the mp3dec filter
+ *
+ * \sa filter::init
+ */
+void mp3dec_init(struct filter *f)
+{
+       f->open = mp3dec_open;
+       f->convert = mp3dec;
+       f->close = mp3dec_close;
+}
diff --git a/mysql.c b/mysql.c
new file mode 100644 (file)
index 0000000..b8fa02d
--- /dev/null
+++ b/mysql.c
@@ -0,0 +1,2539 @@
+/*
+ * Copyright (C) 1999-2006 Andre Noll <maan@systemlinux.org>
+ *
+ *     This program is free software; you can redistribute it and/or modify
+ *     it under the terms of the GNU General Public License as published by
+ *     the Free Software Foundation; either version 2 of the License, or
+ *     (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU General Public License for more details.
+ *
+ *     You should have received a copy of the GNU General Public License
+ *     along with this program; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ */
+
+/** \file mysql.c para_server's mysql-based database tool */
+
+/** \cond some internal constants */
+#define MEDIUM_BLOB_SIZE 16777220 /*  (2**24 + 4) */
+#define BLOB_SIZE 65539 /* (2**16 + 3) */
+/** \endcond */
+#include "server.cmdline.h"
+#include "server.h"
+#include "afs.h"
+#include "db.h"
+#include <mysql/mysql.h>
+#include <mysql/mysql_version.h>
+#include "error.h"
+#include "net.h"
+#include "string.h"
+
+extern struct gengetopt_args_info conf;
+/** pointer to the shared memory area */
+extern struct misc_meta_data *mmd;
+
+static void *mysql_ptr = NULL;
+
+
+static int com_cam(int, int, char **);
+static int com_cdb(int, int, char **);
+static int com_cs(int, int, char **);
+static int com_da(int, int, char **);
+static int com_hist(int, int, char **);
+static int com_info(int, int, char **);
+static int com_laa(int, int, char **);
+static int com_last(int, int, char **);
+static int com_ls(int, int, char **);
+static int com_mbox(int, int, char **);
+static int com_mv(int, int, char **);
+static int com_na(int, int, char **);
+static int com_pic(int, int, char **);
+static int com_picch(int, int, char **);
+static int com_picdel(int, int, char **);
+static int com_piclist(int, int, char **);
+static int com_ps(int, int, char **);
+static int com_rm_ne(int, int, char **);
+static int com_sa(int, int, char **);
+static int com_set(int, int, char **);
+static int com_sl(int, int, char **);
+static int com_stradd_picadd(int, int, char **);
+static int com_streams(int, int, char **);
+static int com_strdel(int, int, char **);
+static int com_strq(int, int, char **);
+static int com_summary(int, int, char **);
+static int com_upd(int, int, char **);
+static int com_us(int, int, char **);
+static int com_verb(int, int, char **);
+static int com_vrfy(int, int, char **);
+
+static struct server_command cmds[] = {
+{
+.name = "cam",
+.handler = com_cam,
+.perms = DB_READ|DB_WRITE,
+.description = "copy all metadata",
+.synopsis = "cam source dest1 [dest2 ...]",
+.help =
+
+"Copy attributes and other meta data from source file to destination\n"
+"file(s). Useful for files that have been renamed.\n"
+
+},
+{
+.name = "cdb",
+.handler = com_cdb,
+.perms = DB_READ|DB_WRITE,
+.description = "create database",
+.synopsis = "cdb [name]",
+.help =
+
+"\tCreate database name containing the initial columns for basic\n"
+"\tinteroperation with server. This command has to be used only once\n"
+"\twhen you use the mysql database tool for the very first time.\n"
+"\n"
+"\tThe optional name defaults to 'paraslash' if not given.\n"
+
+},
+{
+.name = "clean",
+.handler = com_vrfy,
+.perms = DB_READ | DB_WRITE,
+.description = "nuke invalid entries in database",
+.synopsis = "clean",
+.help =
+
+"If the vrfy command shows you any invalid entries in your database,\n"
+"you can get rid of them with clean. Always run 'upd' and 'vrfy'\n"
+"before running this command. Use with caution!\n"
+
+},
+{
+.name = "cs",
+.handler = com_cs,
+.perms = AFS_WRITE | DB_READ | DB_WRITE,
+.description = "change stream",
+.synopsis = "cs [s]",
+.help =
+
+"Selects stream s or prints current stream when s was not given.\n"
+
+},
+{
+.name = "csp",
+.handler = com_cs,
+.perms = AFS_WRITE | DB_READ,
+.description = "change stream and play",
+.synopsis = "csp s",
+.help =
+
+"Select stream s and start playing. If this results in a stream-change,\n"
+"skip rest of current audio file.\n"
+
+},
+{
+.name = "da",
+.handler = com_da,
+.perms = DB_READ | DB_WRITE,
+.description  = "drop attribute from database",
+.synopsis = "da att",
+.help =
+
+"Use with caution. All info on attribute att will be lost.\n"
+
+},
+{
+.name = "hist",
+.handler = com_hist,
+.perms = DB_READ,
+.description = "print history",
+.synopsis = "hist",
+.help =
+
+"Print list of all audio files together with number of days since each\n"
+"file was last played.\n"
+
+},
+{
+.name = "info",
+.handler = com_info,
+.perms = DB_READ,
+.description = "print database info",
+.synopsis = "info [af]",
+.help =
+
+"print database informations for audio file af. Current audio file is\n"
+"used if af is not given.\n"
+
+},
+{
+.name = "la",
+.handler = com_info,
+.perms = DB_READ,
+.description = "list attributes",
+.synopsis = "la [af]",
+.help =
+
+"List attributes of audio file af or of current audio file when invoked\n"
+"without arguments.\n"
+
+},
+{
+.name = "laa",
+.handler = com_laa,
+.perms = DB_READ,
+.description = "list available attributes",
+.synopsis = "laa",
+.help = 
+
+"What should I say more?\n"
+
+},
+{
+.name = "last",
+.handler = com_last,
+.perms = DB_READ,
+.description = "print list of audio files, ordered by lastplayed time",
+.synopsis = "last [n]",
+.help =
+
+"The optional number n defaults to 10 if not specified.\n"
+
+},
+{
+.name = "ls",
+.handler = com_ls,
+.perms = DB_READ,
+.description = "list all audio files that match a LIKE pattern",
+.synopsis = "ls [pattern]",
+.help =
+
+"\tIf pattern was not given, print list of all audio files known\n"
+"\tto the mysql database tool. See the documentation of mysql\n"
+"\tfor the definition of LIKE patterns.\n"
+
+},
+{
+.name = "mbox",
+.handler = com_mbox,
+.perms = DB_READ,
+.description = "dump audio file list in mbox format",
+.synopsis = "mbox [p]",
+.help =
+
+"\tDump list of audio files in mbox format (email) to stdout. If\n"
+"\tthe optional pattern p is given, only those audio files,\n"
+"\twhose basename match p are going to be included. Otherwise,\n"
+"\tall files are selected.\n"
+"\n"
+"EXAMPLE\n"
+"\tThe mbox command can be used together with your favorite\n"
+"\tmailer (this example uses mutt) for browsing the audio file\n"
+"\tcollection:\n"
+"\n"
+"\t\tpara_client mbox > ~/para_mbox\n"
+"\n"
+"\t\tmutt -F ~/.muttrc.para -f ~/para_mbox\n"
+"\n"
+"\tFor playlists, you can use mutt's powerful pattern matching\n"
+"\tlanguage to select files. If you like to tag all files\n"
+"\tcontaining the pattern 'foo', type 'T', then '~s foo'.\n"
+"\n"
+"\tWhen ready with the list, type ';|' (i.e., hit the semicolon\n"
+"\tkey to apply the next mutt command to all tagged messages,\n"
+"\tthen the pipe key) to pipe the selected \"mails\" to a\n"
+"\tsuitable script which adds a paraslash stream where exactly\n"
+"\tthese files are admissable or does whatever thou wilt.\n"
+
+},
+{
+.name = "mv",
+.handler = com_mv,
+.perms = DB_READ | DB_WRITE,
+.description = "rename entry in database",
+.synopsis = "mv oldname newname",
+.help =
+
+"Rename oldname to newname. This updates the data table to reflect the\n"
+"new name. All internal data (numplayed, lastplayed, picid,..) is kept.\n"
+"If newname is a full path, the dir table is updated as well.\n"
+
+},
+{
+.name = "na",
+.handler = com_na,
+.perms = DB_READ | DB_WRITE,
+.description = "add new attribute to database",
+.synopsis = "na att",
+.help =
+
+"This adds a column named att to your mysql database. att should only\n"
+"contain letters and numbers, in paricular, '+' and '-' are not allowed.\n"
+
+},
+{
+.name = "ne",
+.handler = com_rm_ne,
+.perms = DB_READ | DB_WRITE,
+.description = "add new database entries",
+.synopsis = "ne file1 [file2 [...]]",
+.help =
+
+"Add the given filename(s) to the database, where file1,... must\n"
+"be full path names. This command might be much faster than 'upd'\n"
+"if the number of given files is small.\n"
+
+},
+{
+.name = "ns",
+.handler = com_ps,
+.perms = AFS_WRITE | DB_READ | DB_WRITE,
+.description = "change to next stream",
+.synopsis = "ns",
+.help =
+
+"Cycle forwards through stream list.\n"
+
+},
+{
+.name = "pic",
+.handler = com_pic,
+.perms = DB_READ,
+.description = "get picture by name or by identifier",
+.synopsis = "pic [name]",
+.help =
+
+"\tDump jpg image that is associated to given audio file (current\n"
+"\taudio file if not specified) to stdout. If name starts with\n"
+"\t'#' it is interpreted as an identifier instead and the picture\n"
+"\thaving that identifier is dumped to stdout.\n"
+"\n"
+"EXAMPLE\n"
+"\n"
+"\tpara_client pic '#123' > pic123.jpg\n"
+
+},
+{
+.name = "picadd",
+.handler = com_stradd_picadd,
+.perms = DB_READ | DB_WRITE,
+.description = "add picture to database",
+.synopsis = "picadd [picname]",
+.help =
+
+"\tRead jpeg file from stdin and store it as picname in database.\n"
+"\n"
+"EXAMPLE\n"
+"\n"
+"\tpara_client picadd foo.jpg < foo.jpg\n"
+
+},
+{
+.name = "picass",
+.handler = com_set,
+.perms = DB_READ | DB_WRITE,
+.description = "associate a picture to file(s)",
+.synopsis = "picass pic_id file1 [file2...]",
+.help =
+
+"Associate the picture given by pic_id to all given files.\n"
+
+},
+{
+.name = "picch",
+.handler = com_picch,
+.perms = DB_READ | DB_WRITE,
+.description = "change name of picture",
+.synopsis = "picch id new_name",
+.help =
+
+"Asign new_name to picture with identifier id.\n"
+
+},
+{
+.name = "picdel",
+.handler = com_picdel,
+.perms = DB_READ | DB_WRITE,
+.description = "delete picture from database",
+.synopsis = "picdel id1 [id2...]",
+.help =
+
+"Delete each given picture from database.\n"
+
+},
+{
+.name = "piclist",
+.handler = com_piclist,
+.perms = DB_READ,
+.description = "print list of pictures",
+.synopsis = "piclist",
+.help =
+
+"Print id, name and length of each picture contained in the database.\n"
+
+},
+{
+.name = "ps",
+.handler = com_ps,
+.perms = AFS_WRITE | DB_READ | DB_WRITE,
+.description = "change to previous stream",
+.synopsis = "ps",
+.help =
+
+"Cycle backwards through stream list.\n"
+
+},
+{
+.name = "rm",
+.handler = com_rm_ne,
+.perms = DB_READ | DB_WRITE,
+.description = "remove entries from database",
+.synopsis = "rm name1 [name2 [...]]",
+.help =
+
+"Remove name1, name2, ... from the data table. Use with caution\n"
+
+},
+{
+.name = "sa",
+.handler = com_sa,
+.perms = DB_READ | DB_WRITE,
+.description = "set/unset attributes",
+.synopsis = "sa at1<'+' | '-'> [at2<'+' | '-'> ] [af1 ...]",
+//.synopsis = "foo",
+.help =
+
+"Set ('+') or unset ('-') attribute at1, at2 etc. for given list of\n"
+"audio files. If no audio files were given the current audio file is\n"
+"used. Example:\n"
+"\n"
+"sa rock+ punk+ classic- LZ__Waldsterben.mp3\n"
+"\n"
+"sets the 'rock' and the 'punk' attribute but unsets the 'classic'\n"
+"attribute.\n"
+
+},
+{
+.name = "skip",
+.handler = com_sl,
+.perms = DB_READ | DB_WRITE,
+.description = "skip subsequent audio files(s)",
+.synopsis = "skip n [s]",
+.help =
+
+"Skip the next n audio files of stream s. This is equivalent to the\n"
+"command 'sl n s', followed by 'us name' for each name the output of sl.\n"
+
+},
+{
+.name = "sl",
+.handler = com_sl,
+.perms = DB_READ,
+.description = "print score list",
+.synopsis = "sl n [s]",
+.help =
+
+"Print sorted list of maximal n lines. Each line is an admissible entry\n"
+"with respect to stream s. The list is sorted by score-value which is\n"
+"given by the definition of s. If s is not given, the current stream\n"
+"is used. Example:\n"
+"\n"
+"      sl 1\n"
+"\n"
+"shows you the audio file the server would select right now.\n"
+
+},
+{
+.name = "snp",
+.handler = com_set,
+.perms = DB_READ | DB_WRITE,
+.description = "set numplayed",
+.synopsis = "snp number af1 [af2 ...]",
+.help =
+
+"Update the numplayed field in the data table for all given audio files.\n"
+
+},
+{
+.name = "stradd",
+.handler = com_stradd_picadd,
+.perms = DB_READ | DB_WRITE,
+.description = "add stream",
+.synopsis = "stradd s",
+.help =
+
+"Add stream s to the list of available streams. The stream definition\n"
+"for s is read from stdin and is then sent to para_server. Example:\n"
+"\n"
+"      echo 'deny: NAME_LIKE(%Madonna%)' | para_client stradd no_madonna\n"
+"\n"
+"adds the new stream 'no_madonna' to the list of available streams. A given\n"
+"audio file is admissible for this stream iff its basename does not contain the\n"
+"string 'Madonna'.\n"
+
+
+},
+{
+.name = "strdel",
+.handler = com_strdel,
+.perms = DB_READ | DB_WRITE,
+.description = "delete stream",
+.synopsis = "strdel s",
+.help =
+
+"Remove stream s from database.\n"
+
+},
+{
+.name = "streams",
+.handler = com_streams,
+.perms = DB_READ,
+.description = "list streams",
+.synopsis = "streams",
+.help =
+
+"Print list of available streams. Use 'cs' to switch to any of these.\n"
+
+},
+{
+.name = "strq",
+.handler = com_strq,
+.perms = DB_READ,
+.description = "query stream definition",
+.synopsis = "strq [s]",
+.help =
+
+"Print definition of stream s to stdout. Use current stream if s was\n"
+"not given.\n"
+
+},
+{
+.name = "summary",
+.handler = com_summary,
+.perms = DB_READ,
+.description = "list attributes",
+.synopsis = "summary",
+.help =
+
+"\tPrint a list of attributes together with number of audio\n"
+"\tfiles having that attribute set.\n"
+
+},
+{
+.name = "upd",
+.handler = com_upd,
+.perms = DB_READ | DB_WRITE,
+.description = "update database",
+.synopsis = "upd",
+.help =
+
+"This command uses the --audio_file_dir option of para_server to locate\n"
+"your audio files. New files are then added to the mysql database. Use\n"
+"this command if you got new files or if you have moved some files\n"
+"around.\n"
+
+},
+{
+.name = "us",
+.handler = com_us,
+.perms = DB_READ | DB_WRITE,
+.description = "update lastplayed time",
+.synopsis = "us name",
+.help =
+
+"Update lastplayed time without actually playing the thing.\n"
+
+},
+{
+.name = "verb",
+.handler = com_verb,
+.perms = DB_READ | DB_WRITE,
+.description = "send verbatim sql query",
+.synopsis = "verb cmd",
+.help =
+
+"Send cmd to mysql server. For expert/debugging only. Note that cmd\n"
+"usually must be escaped. Use only if you know what you are doing!\n"
+
+},
+{
+.name = "vrfy",
+.handler = com_vrfy,
+.perms = DB_READ,
+.description = "list invalid entries in database",
+.synopsis = "vrfy",
+.help =
+
+"Show what clean would delete. Run 'upd' before this command to make\n"
+"sure your database is up to date.\n"
+
+},
+{
+.name = NULL,
+}
+};
+
+static struct para_macro macro_list[] = {
+       {       .name = "IS_N_SET",
+               .replacement = "(data.%s != '1')"
+       }, {
+               .name = "IS_SET",
+               .replacement = "(data.%s = '1')"
+       }, {
+               .name = "PICID",
+               .replacement = "%sdata.Pic_Id"
+       }, {
+               .name = "NAME_LIKE",
+               .replacement = "(data.name like '%s')"
+       }, {
+               .name = "LASTPLAYED",
+               .replacement = "%sFLOOR((UNIX_TIMESTAMP(now())"
+                       "-UNIX_TIMESTAMP(data.Lastplayed))/60)"
+       }, {
+               .name = "NUMPLAYED",
+               .replacement = "%sdata.Numplayed"
+       }, {
+               .name = NULL,
+       }
+};
+
+static int real_query(char *query)
+{
+       if (!mysql_ptr)
+               return -E_NOTCONN;
+       PARA_DEBUG_LOG("%s\n", query);
+       if (mysql_real_query(mysql_ptr, query, strlen(query))) {
+               PARA_ERROR_LOG("real_query error (%s)\n",
+                       mysql_error(mysql_ptr));
+               return -E_QFAILED;
+       }
+       return 1;
+}
+
+/*
+ * Use open connection given by mysql_ptr to query server. Returns a
+ * result pointer on succes and NULL on errors
+ */
+static struct MYSQL_RES *get_result(char *query)
+{
+       void *result;
+
+       if (real_query(query) < 0)
+               return NULL;
+       result = mysql_store_result(mysql_ptr);
+       if (!result)
+               PARA_ERROR_LOG("%s", "store_result error\n");
+       return result;
+}
+/*
+ * write input from fd to dynamically allocated char array,
+ * but maximal max_size byte. Return size.
+ */
+static int fd2buf(int fd, char **buf_ptr, size_t max_size)
+{
+       const size_t chunk_size = 1024;
+       size_t size = 2048;
+       char *buf = para_malloc(size * sizeof(char)), *p = buf;
+       int ret;
+
+       while ((ret = recv_bin_buffer(fd, p, chunk_size)) > 0) {
+               p += ret;
+               if ((p - buf) + chunk_size >= size) {
+                       char *tmp;
+
+                       size *= 2;
+                       if (size > max_size) {
+                               ret = -E_TOOBIG;
+                               goto out;
+                       }
+                       tmp = para_realloc(buf, size);
+                       p = (p - buf) + tmp;
+                       buf = tmp;
+               }
+       }
+       if (ret < 0)
+               goto out;
+       *buf_ptr = buf;
+       ret = p - buf;
+out:
+       if (ret < 0 && buf)
+               free(buf);
+       return ret;
+}
+
+static char *escape_blob(char* old, int size)
+{
+       char *new;
+
+       if (!mysql_ptr || size < 0)
+               return NULL;
+       new = para_malloc(2 * size * sizeof(char) + 1);
+       mysql_real_escape_string(mysql_ptr, new, old, size);
+       return new;
+}
+
+static char *escape_str(char* old)
+{
+       return escape_blob(old, strlen(old));
+}
+
+static char *escaped_basename(const char *name)
+{
+       char *esc, *bn = para_basename(name);
+
+       if (!bn)
+               return NULL;
+       esc = escape_str(bn);
+       free(bn);
+       return esc;
+}
+
+/*
+ * new attribute
+ */
+static int com_na(__unused int fd, int argc, char *argv[])
+{
+       char *q;
+       int ret;
+
+       if (argc < 1)
+               return -E_MYSQL_SYNTAX;
+       q = make_message("alter table data add %s char(1) "
+               "not null default 0", argv[1]);
+       ret = real_query(q);
+       free(q);
+       return ret;
+}
+
+/*
+ * delete attribute
+ */
+static int com_da(__unused int fd, int argc, char *argv[])
+{
+       char *q;
+       int ret;
+
+       if (argc < 1)
+               return -E_MYSQL_SYNTAX;
+       q = make_message("alter table data drop %s", argv[1]);
+       ret = real_query(q);
+       free(q);
+       return ret;
+}
+
+/* stradd/pic_add */
+static int com_stradd_picadd(int fd, int argc, char *argv[])
+{
+       char *blob = NULL, *esc_blob = NULL, *q;
+       const char *fmt, *del_fmt;
+       int ret, stradd = strcmp(argv[0], "picadd");
+       size_t size;
+
+       if (argc < 1)
+               return -E_MYSQL_SYNTAX;
+       if (strlen(argv[1]) >= MAXLINE - 1)
+               return -E_NAMETOOLONG;
+       if (!mysql_ptr)
+               return -E_NOTCONN;
+       if (stradd) {
+               size = BLOB_SIZE;
+               fmt = "insert into streams (name, def) values ('%s','%s')";
+               del_fmt="delete from streams where name='%s'";
+       } else {
+               size = MEDIUM_BLOB_SIZE;
+               fmt = "insert into pics (name, pic) values ('%s','%s')";
+               del_fmt="delete from pics where pic='%s'";
+       }
+       q  = make_message(del_fmt, argv[1]);
+       ret = real_query(q);
+       free(q);
+       if (ret < 0)
+               return ret;
+       if ((ret = send_buffer(fd, AWAITING_DATA_MSG) < 0))
+               return ret;
+       if ((ret = fd2buf(fd, &blob, size)) < 0)
+               return ret;
+       PARA_DEBUG_LOG("length: %i\n", ret);
+       size = ret;
+       if (stradd)
+               blob[size] = '\0';
+       esc_blob = escape_blob(blob, ret);
+       free(blob);
+       if (!esc_blob)
+               return -E_TOOBIG;
+       q = make_message(fmt, argv[1], esc_blob);
+       free(esc_blob);
+       ret = real_query(q);
+       free(q);
+       return ret;
+}
+
+/*
+ * print results to fd
+ */
+static int print_results(int fd, void *result,
+               unsigned int top, unsigned int left,
+               unsigned int bottom, unsigned int right)
+{
+       unsigned int i,j;
+       int ret;
+       MYSQL_ROW row;
+
+       for (i = top; i <= bottom; i++) {
+               row = mysql_fetch_row(result);
+               if (!row || !row[0])
+                       return -E_NOROW;
+               for (j = left; j <= right; j++) {
+                       ret = send_va_buffer(fd, j == left? "%s" : "\t%s",
+                                       row[j]?  row[j] : "NULL");
+                       if (ret < 0)
+                               return ret;
+               }
+               ret = send_buffer(fd, "\n");
+               if (ret < 0)
+                       return ret;
+       }
+       return 0;
+}
+
+/*
+ * verbatim
+ */
+static int com_verb(int fd, int argc, char *argv[])
+{
+       void *result = NULL;
+       int ret;
+       unsigned int num_rows, num_fields;
+
+       if (argc < 1)
+               return -E_MYSQL_SYNTAX;
+        result = get_result(argv[1]);
+       if (!result)
+               /* return success, because it's ok to have no results */
+               return 1;
+       num_fields = mysql_field_count(mysql_ptr);
+       num_rows = mysql_num_rows(result);
+       ret = 1;
+       if (num_fields && num_rows)
+               ret = print_results(fd, result, 0, 0, num_rows - 1,
+                       num_fields - 1);
+       mysql_free_result(result);
+       return ret;
+}
+
+/* returns NULL on errors or if there are no atts defined yet */
+static void *get_all_attributes(void)
+{
+       void *result = get_result("desc data");
+       unsigned int num_rows;
+
+       if (!result)
+               return NULL;
+       num_rows = mysql_num_rows(result);
+       if (num_rows < 5) {
+               mysql_free_result(result);
+               return NULL;
+       }
+        mysql_data_seek(result, 4); /* skip Lastplayed, Numplayed... */
+       return result;
+}
+
+/*
+ * list all attributes
+ */
+static int com_laa(int fd, int argc, __unused char *argv[])
+{
+       void *result;
+       int ret;
+
+       if (argc)
+               return -E_MYSQL_SYNTAX;
+       result = get_all_attributes();
+       if (!result)
+               return -E_NOATTS;
+       ret = print_results(fd, result, 0, 0, mysql_num_rows(result) - 5, 0);
+       mysql_free_result(result);
+       return ret;
+}
+
+/*
+ * history
+ */
+static int com_hist(int fd, int argc, char *argv[]) {
+       int ret;
+       void *result = NULL;
+       char *q;
+       unsigned int num_rows;
+
+       q = make_message("select name, to_days(now()) - to_days(lastplayed) from "
+               "data%s%s%s order by lastplayed",
+               (argc < 1)? "" : " where ",
+               (argc < 1)? "" : argv[1], 
+               (argc < 1)? "" : " = '1'");
+        result = get_result(q);
+       free(q);
+       if (!result)
+               return -E_NORESULT;
+       num_rows = mysql_num_rows(result);
+       ret = 1;
+       if (num_rows)
+               ret = print_results(fd, result, 0, 0, num_rows - 1, 1);
+       mysql_free_result(result);
+       return ret;
+}
+
+/*
+ * get last num audio files
+ */
+static int com_last(int fd, int argc, char *argv[])
+{
+       void *result = NULL;
+       char *q;
+       int num, ret;
+
+       if (argc < 1)
+               num = 10;
+       else
+               num = atoi(argv[1]);
+       if (!num)
+               return -E_MYSQL_SYNTAX;
+       q = make_message("select name from data order by lastplayed desc "
+               "limit %u", num);
+       result = get_result(q);
+       free(q);
+       if (!result)
+               return -E_NORESULT;
+       ret = print_results(fd, result, 0, 0, mysql_num_rows(result) - 1, 0);
+       mysql_free_result(result);
+       return ret;
+}
+
+static int com_mbox(int fd, int argc, char *argv[])
+{
+       void *result;
+       MYSQL_ROW row;
+       int ret;
+       unsigned int num_rows, num_fields;
+       char *query = para_strdup("select concat('From foo@localhost ', "
+               "date_format(Lastplayed, '%a %b %e %T %Y'), "
+               "'\nReceived: from\nTo: bar\n");
+
+       ret = -E_NOATTS;
+       result = get_all_attributes();
+       if (!result)
+               goto out;
+       ret = -E_NOROW;
+       while ((row = mysql_fetch_row(result))) {
+               char *tmp;
+
+               if (!row[0])
+                       goto out;
+               tmp = make_message("%s X-Attribute-%s: ', %s, '\n", query,
+                       row[0], row[0]);
+               free(query);
+               query = tmp;
+       }
+       query = para_strcat(query,
+               "From: a\n"
+               "Subject: "
+               "', name, '"
+               "\n\n\n"
+               "') from data"
+       );
+       if (argc >= 1) {
+               char *tmp = make_message("%s where name LIKE '%s'", query,
+                       argv[1]);
+               free(query);
+               query = tmp;
+       }
+       mysql_free_result(result);
+       ret = -E_NORESULT;
+        result = get_result(query);
+       if (!result)
+               goto out;
+       ret = -E_EMPTY_RESULT;
+       num_fields = mysql_field_count(mysql_ptr);
+       num_rows = mysql_num_rows(result);
+       if (!num_fields || !num_rows)
+               goto out;
+       ret = print_results(fd, result, 0, 0, num_rows - 1, num_fields - 1);
+out:
+       free(query);
+       if (result)
+               mysql_free_result(result);
+       return ret;
+}
+
+/* get attributes by name. If verbose is not 0, get_a writes a string
+ * into atts of the form 'att1="0",att2="1"', which is used in com_cam
+ * for contructing a mysql update query.
+ * never returns NULL in *NON VERBOSE* mode
+ */
+static char *get_atts(char *name, int verbose)
+{
+       char *atts = NULL, *buf, *ebn;
+       void *result = NULL, *result2 = NULL;
+       MYSQL_ROW row, row2;
+       int i, ret;
+       unsigned int num_fields;
+
+       ret = -E_NOATTS;
+       result2 = get_all_attributes();
+       if (!result2)
+               goto out;
+       ret = -E_ESCAPE;
+       if (!(ebn = escaped_basename(name)))
+               goto out;
+       buf = make_message("select * from data where name='%s'", ebn);
+       free(ebn);
+       ret = -E_NORESULT;
+       result = get_result(buf);
+       free(buf);
+       if (!result)
+               goto out;
+       ret = -E_EMPTY_RESULT;
+       num_fields = mysql_num_fields(result);
+       if (num_fields < 5)
+               goto out;
+        mysql_data_seek(result2, 4); /* skip Lastplayed, Numplayed... */
+       row = mysql_fetch_row(result);
+       ret = -E_NOROW;
+       if (!row)
+               goto out;
+       for (i = 4; i < num_fields; i++) {
+               int is_set = row[i] && !strcmp(row[i], "1");
+               row2 = mysql_fetch_row(result2);
+               if (!row2 || !row2[0])
+                       goto out;
+               if (atts && (verbose || is_set))
+                       atts = para_strcat(atts, verbose? "," : " ");
+                if (is_set || verbose)
+                       atts = para_strcat(atts, row2[0]);
+               if (verbose)
+                       atts = para_strcat(atts, is_set? "=\"1\"" : "=\"0\"");
+        }
+       ret = 1;
+out:
+       if (result2)
+               mysql_free_result(result2);
+       if (result)
+               mysql_free_result(result);
+       if (!atts && !verbose)
+               atts = para_strdup("(none)");
+       return atts;
+}
+
+/* never returns NULL in verbose mode */
+static char *get_meta(char *name, int verbose)
+{
+       MYSQL_ROW row;
+       void *result = NULL;
+       char *ebn, *q, *ret = NULL;
+       const char *verbose_fmt =
+               "select concat('lastplayed: ', "
+               "(to_days(now()) - to_days(lastplayed)),"
+               "' day(s). numplayed: ', numplayed, "
+               "', pic: ', pic_id) "
+               "from data where name = '%s'";
+       /* is that really needed? */
+       const char *fmt = "select concat('lastplayed=\\'', lastplayed, "
+               "'\\', numplayed=\\'', numplayed, "
+               "'\\', pic_id=\\'', pic_id, '\\'') "
+               "from data where name = '%s'";
+
+       if (!(ebn = escaped_basename(name)))
+               goto out;
+       q = make_message(verbose? verbose_fmt : fmt, ebn);
+       free(ebn);
+        result = get_result(q);
+       free(q);
+       if (!result)
+               goto out;
+       row = mysql_fetch_row(result);
+       if (!row || !row[0])
+               goto out;
+       ret = para_strdup(row[0]);
+out:
+       if (result)
+               mysql_free_result(result);
+       if (!ret && verbose)
+               ret = para_strdup("(not yet played)");
+       return ret;
+}
+
+static char *get_dir(char *name)
+{
+       char *ret = NULL, *q, *ebn;
+       void *result;
+       MYSQL_ROW row;
+
+       if (!(ebn = escaped_basename(name)))
+               return NULL;
+       q = make_message("select dir from dir where name = '%s'", ebn);
+       free(ebn);
+       result = get_result(q);
+       free(q);
+       if (!result)
+               return NULL;
+       row = mysql_fetch_row(result);
+       if (row && row[0])
+               ret = para_strdup(row[0]);
+       mysql_free_result(result);
+       return ret;
+}
+
+/* never returns NULL */
+static char *get_current_stream(void)
+{
+       char *ret;
+       MYSQL_ROW row;
+       void *result = get_result("select def from streams where "
+               "name = 'current_stream'");
+
+       if (!result)
+               goto err_out;
+       row = mysql_fetch_row(result);
+       if (!row || !row[0])
+               goto err_out;
+       ret = para_strdup(row[0]);
+       mysql_free_result(result);
+       return ret;
+err_out:
+       if (result)
+               mysql_free_result(result);
+       return para_strdup("(none)");
+}
+/*
+ * Read stream definition of stream streamname and construct mysql
+ * query. Return NULL on errors. If streamname is NULL, use current
+ * stream. If that is also NULL, use query that selects everything.
+ * If filename is NULL, query will list everything, otherwise only
+ * the score of given file.
+ */
+static char *get_query(char *streamname, char *filename, int with_path)
+{
+       char *accept_opts = NULL, *deny_opts = NULL, *score = NULL;
+       char *where_clause, *order, *query;
+       char command[255] = ""; /* buffer for sscanf */
+       void *result;
+       MYSQL_ROW row;
+       char *end, *tmp;
+       char *select_clause = NULL;
+       if (!streamname)
+               tmp = get_current_stream();
+       else
+               tmp = para_strdup(streamname);
+       if (!strcmp(tmp, "(none)")) {
+               free(tmp);
+               if (filename) {
+                       char *ret, *ebn = escaped_basename(filename);
+                       ret = make_message("select to_days(now()) - "
+                               "to_days(lastplayed) from data "
+                               "where name = '%s'", ebn);
+                       free(ebn);
+                       return ret;
+               }
+               if (with_path)
+                       return make_message(
+                               "select concat(dir.dir, '/', dir.name) "
+                               "from data, dir where dir.name = data.name "
+                               "order by data.lastplayed"
+                       );
+               return make_message(
+                       "select name from data where name is not NULL "
+                       "order by lastplayed"
+               );
+       }
+       free(tmp);
+       query = make_message("select def from streams where name = '%s'",
+               streamname);
+       result = get_result(query);
+       free(query);
+       query = NULL;
+       if (!result)
+               goto out;
+       row = mysql_fetch_row(result);
+       if (!row || !row[0])
+               goto out;
+       end = row[0];
+       while (*end) {
+               int n;
+               char *arg, *line = end;
+
+               if (!(end = strchr(line, '\n')))
+                       break;
+               *end = '\0';
+               end++;
+               if (sscanf(line, "%200s%n", command, &n) < 1)
+                       continue;
+               arg = line + n;
+               if (!strcmp(command, "accept:")) {
+                       char *tmp2 = s_a_r_list(macro_list, arg);
+                       if (accept_opts)
+                               accept_opts = para_strcat(
+                                       accept_opts, " or ");
+                       accept_opts = para_strcat(accept_opts, tmp2);
+                       free(tmp2);
+                       continue;
+               }
+               if (!strcmp(command, "deny:")) {
+                       char *tmp2 = s_a_r_list(macro_list, arg);
+                       if (deny_opts)
+                               deny_opts = para_strcat(deny_opts, " or ");
+                       deny_opts = para_strcat(deny_opts, tmp2);
+                       free(tmp2);
+                       continue;
+               }
+               if (!strcmp(command, "score:"))
+                       score = s_a_r_list(macro_list, arg);
+       }
+       if (!score) {
+               score = s_a_r_list(macro_list, conf.mysql_default_score_arg);
+               if (!score)
+                       goto out;
+       }
+       if (filename) {
+               char *ebn = escaped_basename(filename);
+               if (!ebn)
+                       goto out;
+               select_clause = make_message("select %s from data ", score);
+               free(score);
+               where_clause = make_message( "where name = '%s' ", ebn);
+               free(ebn);
+               order = para_strdup("");
+               goto write_query;
+       }
+       select_clause = para_strdup(with_path?
+                "select concat(dir.dir, '/', dir.name) from data, dir "
+                "where dir.name = data.name "
+                :
+                "select name from data where name is not NULL");
+       order = make_message("order by -(%s)", score);
+       free(score);
+       if (accept_opts && deny_opts) {
+               where_clause = make_message("and ((%s) and not (%s)) ",
+                       accept_opts, deny_opts);
+               goto write_query;
+       }
+       if (accept_opts && !deny_opts) {
+               where_clause = make_message("and (%s) ", accept_opts);
+               goto write_query;
+       }
+       if (!accept_opts && deny_opts) {
+               where_clause = make_message("and not (%s) ", deny_opts);
+               goto write_query;
+       }
+       where_clause = para_strdup("");
+write_query:
+       query = make_message("%s %s %s", select_clause, where_clause, order);
+       free(order);
+       free(select_clause);
+       free(where_clause);
+out:
+       if (accept_opts)
+               free(accept_opts);
+       if (deny_opts)
+               free(deny_opts);
+       if (result)
+               mysql_free_result(result);
+       return query;
+}
+
+
+
+/*
+ * This is called from server and from some commands. Name must not be NULL
+ * Never returns NULL.
+ */
+static char *get_dbinfo(char *name)
+{
+       char *meta = NULL, *atts = NULL, *info, *dir = NULL, *query, *stream = NULL;
+       void *result = NULL;
+       MYSQL_ROW row = NULL;
+
+       if (!name)
+               return para_strdup("(none)");
+       stream = get_current_stream();
+       meta = get_meta(name, 1);
+       atts = get_atts(name, 0);
+       dir = get_dir(name);
+       /* get score */
+       query = get_query(stream, name, 0);
+       if (!query)
+               goto write;
+       result = get_result(query);
+       free(query);
+       if (result)
+               row = mysql_fetch_row(result);
+write:
+       info = make_message("dbinfo1:dir: %s\n"
+               "dbinfo2:stream: %s, %s, score: %s\n"
+               "dbinfo3:%s\n",
+               dir? dir : "(not contained in table)",
+               stream, meta, 
+               (result && row && row[0])? row[0] : "(no score)",
+               atts);
+       if (dir)
+               free(dir);
+       if (meta)
+               free(meta);
+       if (atts)
+               free(atts);
+       if (stream)
+               free(stream);
+       if (result)
+               mysql_free_result(result);
+       return info;
+}
+
+
+/* might return NULL */
+static char *get_current_audio_file(void)
+{
+       char *name;
+       mmd_lock();
+       name = para_basename(mmd->filename);
+       mmd_unlock();
+       return name;
+}
+
+
+/* print database info */
+static int com_info(int fd, int argc, char *argv[])
+{
+       char *name = NULL, *meta = NULL, *atts = NULL, *dir = NULL;
+       int ret, com_la = strcmp(argv[0], "info");
+
+       if (argc < 1) {
+               ret = -E_GET_AUDIO_FILE;
+               if (!(name = get_current_audio_file()))
+                       goto out;
+               ret = send_va_buffer(fd, "%s\n", name);
+               if (ret < 0)
+                       goto out;
+       } else {
+               ret = -E_ESCAPE;
+               if (!(name = escaped_basename(argv[1])))
+                       goto out;
+       }
+       meta = get_meta(name, 1);
+       atts = get_atts(name, 0);
+       dir = get_dir(name);
+       if (com_la)
+               ret = send_va_buffer(fd, "%s\n", atts);
+       else
+               ret = send_va_buffer(fd, "dir: %s\n" "%s\n" "attributes: %s\n",
+                       dir? dir : "(not contained in table)", meta, atts);
+out:
+       if (meta)
+               free(meta);
+       if (atts)
+               free(atts);
+       if (dir)
+               free(dir);
+       if (name)
+               free(name);
+       return ret;
+}
+static int change_stream(char *stream)
+{
+       char *query;
+       int ret;
+       /* try to insert if it does not exist (compatibility) */
+//     query = make_message("insert into streams (name, def) values "
+//             "('current_stream', '%s')", stream);
+//     real_query(query); /* ignore return value */
+//     free(query);
+       query = make_message("update streams set def='%s' "
+               "where name = 'current_stream'", stream);
+       ret = real_query(query);
+       free(query);
+       return ret;
+}
+
+static int get_pic_id_by_name(char *name)
+{
+       char *q, *ebn;
+       void *result = NULL;
+       long unsigned ret;
+       MYSQL_ROW row;
+
+       if (!(ebn = escaped_basename(name)))
+               return -E_ESCAPE;
+       q = make_message("select pic_id from data where name = '%s'", ebn);
+       free(ebn);
+       result = get_result(q);
+       free(q);
+       if (!result)
+               return -E_NORESULT;
+       row = mysql_fetch_row(result);
+       ret = -E_NOROW;
+       if (row && row[0])
+               ret = atol(row[0]);
+       mysql_free_result(result);
+       return ret;
+}
+
+static int remove_entry(const char *name)
+{
+       char *q, *ebn = escaped_basename(name);
+       int ret = -E_ESCAPE;
+
+       if (!ebn || !*ebn)
+               goto out;
+       q = make_message("delete from data where name = '%s'", ebn);
+       real_query(q); /* ignore errors */
+       free(q);
+       q = make_message("delete from dir where name = '%s'", ebn);
+       real_query(q); /* ignore errors */
+       free(q);
+       ret = 1;
+out:
+       free(ebn);
+       return ret;
+}
+
+static int add_entry(const char *name)
+{
+       char *q, *dn, *ebn = NULL, *edn = NULL;
+       int ret;
+
+       if (!name || !*name)
+               return -E_MYSQL_SYNTAX;
+       ebn = escaped_basename(name);
+       if (!ebn)
+               return -E_ESCAPE;
+       ret = -E_MYSQL_SYNTAX;
+       dn = para_dirname(name);
+       if (!dn)
+               goto out;
+       ret = -E_ESCAPE;
+       edn = escape_str(dn);
+       free(dn);
+       if (!edn || !*edn)
+               goto out;
+       q = make_message("insert into data (name, pic_id) values "
+                       "('%s', '%s')", ebn, "1");
+       ret = real_query(q);
+//     ret = 1; PARA_DEBUG_LOG("q: %s\n", q);
+       free(q);
+       if (ret < 0)
+               goto out;
+       q = make_message("insert into dir (name, dir) values "
+                       "('%s', '%s')", ebn, edn);
+//     ret = 1; PARA_DEBUG_LOG("q: %s\n", q);
+       ret = real_query(q);
+       free(q);
+out:
+       if (ebn)
+               free(ebn);
+       if (edn)
+               free(edn);
+       return ret;
+}
+
+/*
+ * remove/add entries
+ */
+static int com_rm_ne(__unused int fd, int argc, char *argv[])
+{
+       int ne = !strcmp(argv[0], "ne");
+       int i, ret;
+       if (argc < 1)
+               return -E_MYSQL_SYNTAX;
+       for (i = 1; i <= argc; i++) {
+               ret = remove_entry(argv[i]);
+               if (ret < 0)
+                       return ret;
+               if (!ne)
+                       continue;
+               ret = add_entry(argv[i]);
+               if (ret < 0)
+                       return ret;
+       }
+       return 1;
+}
+
+/*
+ * mv: rename entry
+ */
+static int com_mv(__unused int fd, int argc, char *argv[])
+{
+       char *q, *dn, *ebn1 = NULL, *ebn2 = NULL, *edn = NULL;
+       int ret;
+
+       if (argc != 2)
+               return -E_MYSQL_SYNTAX;
+       ebn1 = escaped_basename(argv[1]);
+       ebn2 = escaped_basename(argv[2]);
+       dn = para_dirname(argv[2]);
+       edn = escape_str(dn);
+       free(dn);
+       ret = -E_ESCAPE;
+       if (!ebn1 || !ebn2)
+               goto out;
+       remove_entry(ebn2);
+       q = make_message("update data set name = '%s' where name = '%s'",
+               ebn2, ebn1);
+       ret = real_query(q);
+       free(q);
+       if (ret < 0)
+               goto out;
+       q = make_message("update dir set name = '%s' where name = '%s'",
+               ebn2, ebn1);
+       ret = real_query(q);
+       free(q);
+       if (ret < 0)
+               goto out;
+       /* do not touch table dir, return success if argv[2] is no full path */
+       ret = 1;
+       if (!edn || !*edn)
+               goto out;
+       q = make_message("update dir set dir = '%s' where name = '%s'",
+               edn, ebn2);
+//     PARA_DEBUG_LOG("q: %s\n", q);
+       ret = real_query(q);
+       free(q);
+out:
+       if (ebn1)
+               free(ebn1);
+       if (ebn2)
+               free(ebn2);
+       if (edn)
+               free(edn);
+       return ret;
+
+}
+
+/*
+ * picass: associate pic to audio file
+ * snp: set numplayed
+ */
+static int com_set(__unused int fd, int argc, char *argv[])
+{
+       char *q, *ebn;
+       long unsigned id;
+       int i, ret;
+       char *field = strcmp(argv[0], "picass")? "numplayed" : "pic_id";
+
+       if (argc < 2)
+               return -E_MYSQL_SYNTAX;
+       id = atol(argv[1]);
+       for (i = 2; i <= argc; i++) {
+               ebn = escaped_basename(argv[i]);
+               if (!ebn)
+                       return -E_ESCAPE;
+               q = make_message("update data set %s = %lu "
+                               "where name = '%s'", field, id, ebn);
+               free(ebn);
+               ret = real_query(q);
+               free(q);
+               if (ret < 0)
+                       return ret;
+       }
+       return 1;
+}
+
+/*
+ * picch: change entry's name in pics table
+ */
+static int com_picch(__unused int fd, int argc, char *argv[])
+{
+       int ret;
+       long unsigned id;
+       char *q;
+
+       if (argc != 2)
+               return -E_MYSQL_SYNTAX;
+       id = atol(argv[1]);
+       if (strlen(argv[2]) > MAXLINE)
+               return -E_NAMETOOLONG;
+       q = make_message("update pics set name = '%s' where id = %lu", argv[2], id);
+       ret = real_query(q);
+       free(q);
+       return ret;
+}
+
+/*
+ * piclist: print list of pics in db
+ */
+static int com_piclist(__unused int fd, int argc, __unused char *argv[])
+{
+       void *result = NULL;
+       MYSQL_ROW row;
+       unsigned long *length;
+       int ret;
+
+       if (argc)
+               return -E_MYSQL_SYNTAX;
+       result = get_result("select id,name,pic from pics order by id");
+       if (!result)
+               return -E_NORESULT;
+       while ((row = mysql_fetch_row(result))) {
+               length = mysql_fetch_lengths(result);
+               if (!row || !row[0] || !row[1] || !row[2])
+                       continue;
+               ret = send_va_buffer(fd, "%s\t%lu\t%s\n", row[0], length[2], row[1]);
+               if (ret < 0)
+                       goto out;
+       }
+       ret = 1;
+out:
+       mysql_free_result(result);
+       return ret;
+}
+
+/*
+ * picdel: delete picture from database
+ */
+static int com_picdel(int fd, int argc, char *argv[])
+{
+       char *q;
+       long unsigned id;
+       my_ulonglong aff;
+       int i, ret;
+
+       if (argc < 1)
+               return -E_MYSQL_SYNTAX;
+       for (i = 1; i <= argc; i++) {
+               id = atol(argv[i]);
+               q = make_message("delete from pics where id = %lu", id);
+               ret = real_query(q);
+               free(q);
+               if (ret < 0)
+                       return ret;
+               aff = mysql_affected_rows(mysql_ptr);
+               if (!aff) {
+                       ret = send_va_buffer(fd, "No such id: %lu\n", id);
+                       if (ret < 0)
+                               return ret;
+                       continue;
+               }
+               q = make_message("update data set pic_id = 1 where pic_id = %lu", id);
+               ret = real_query(q);
+               free(q);
+       }
+       return 1;
+}
+/*
+ * pic: get picture by name or by number
+ */
+static int com_pic(int fd, int argc, char *argv[])
+{
+       void *result = NULL;
+       MYSQL_ROW row;
+       unsigned long *length, id;
+       int ret;
+       char *q, *name = NULL;
+
+       if (argc < 1) {
+               ret = -E_GET_AUDIO_FILE;
+               name = get_current_audio_file();
+       } else {
+               ret = -E_ESCAPE;
+               name = escaped_basename(argv[1]);
+       }
+       if (!name)
+               return ret;
+       if (*name == '#')
+               id = atoi(name + 1);
+       else
+               id = get_pic_id_by_name(name);
+       free(name);
+       if (id <= 0)
+               return id;
+       q = make_message("select pic from pics where id = '%lu'", id);
+       result = get_result(q);
+       free(q);
+       if (!result)
+               return -E_NORESULT;
+       row = mysql_fetch_row(result);
+       ret = -E_NOROW;
+       if (!row || !row[0])
+               goto out;
+       length = mysql_fetch_lengths(result);
+       ret = send_bin_buffer(fd, row[0], *length);
+out:
+       mysql_free_result(result);
+       return ret;
+}
+
+/* strdel */
+static int com_strdel(__unused int fd, int argc, char *argv[])
+{
+       char *tmp;
+       int ret = -1;
+
+       if (argc < 1)
+               return -E_MYSQL_SYNTAX;
+       tmp = make_message("delete from streams where name='%s'", argv[1]);
+       ret = real_query(tmp);
+       free(tmp);
+       if (ret < 0)
+               return ret;
+       tmp = get_current_stream();
+       ret = 1;
+       if (strcmp(tmp, "(none)") && !strcmp(tmp, argv[1]))
+               ret = change_stream("(none)");
+       return ret;
+}
+
+/*
+ * ls
+ */
+static int com_ls(int fd, int argc, char *argv[])
+{
+       char *q;
+       void *result;
+       int ret;
+       unsigned int num_rows;
+
+       if (argc > 0)
+               q = make_message("select name from data where name LIKE '%s'",
+                       argv[1]);
+       else
+               q = para_strdup("select name from data");
+        result = get_result(q);
+       free(q);
+       if (!result)
+               return -E_NORESULT;
+       num_rows = mysql_num_rows(result);
+       ret = 1;
+       if (num_rows)
+               ret = print_results(fd, result, 0, 0, num_rows - 1, 0);
+       mysql_free_result(result);
+       return ret;
+}
+/*
+ * summary
+ */
+static int com_summary(__unused int fd, int argc, __unused char *argv[])
+{
+       MYSQL_ROW row;
+       MYSQL_ROW row2;
+       void *result;
+       void *result2 = NULL;
+       const char *fmt = "select count(name) from data where %s='1'";
+       int ret = -E_NORESULT;
+
+       if (argc)
+               return -E_MYSQL_SYNTAX;
+       result = get_all_attributes();
+       if (!result)
+               goto out;
+       while ((row = mysql_fetch_row(result))) {
+               char *buf;
+
+               ret = -E_NOROW;
+               if (!row[0])
+                       goto out;
+               ret = -E_NORESULT;
+               buf = make_message(fmt, row[0]);
+               result2 = get_result(buf);
+               free(buf);
+               if (!result2)
+                       goto out;
+               ret = -E_NOROW;
+               row2 = mysql_fetch_row(result2);
+               if (!row2 || !row2[0])
+                       goto out;
+               ret = send_va_buffer(fd, "%s\t%s\n", row[0], row2[0]);
+               if (ret < 0)
+                       goto out;
+       }
+       ret = 1;
+out:
+       if (result2)
+               mysql_free_result(result2);
+       if (result)
+               mysql_free_result(result);
+       return ret;
+}
+
+static int get_numplayed(char *name)
+{
+       void *result;
+       MYSQL_ROW row;
+       const char *fmt = "select numplayed from data where name = '%s'";
+       char *buf = make_message(fmt, name);
+       int ret = -E_NORESULT;
+
+        result = get_result(buf);
+       free(buf);
+       if (!result)
+               goto out;
+       ret = -E_NOROW;
+       row = mysql_fetch_row(result);
+       if (!row || !row[0])
+               goto out;
+       ret = atoi(row[0]);
+out:
+       if (result)
+               mysql_free_result(result);
+       return ret;
+}
+
+static int update_audio_file(char *name)
+{
+       int ret;
+       const char *fmt1 = "update data set lastplayed = now() where name = '%s'";
+       const char *fmt2 = "update data set numplayed = %i where name = '%s'";
+       char *q;
+       char *ebn = escaped_basename(name);
+
+       ret = -E_ESCAPE;
+       if (!ebn)
+               goto out;
+       q = make_message(fmt1, ebn);
+       ret = real_query(q);
+       free(q);
+       if (ret < 0)
+               goto out;
+       ret = get_numplayed(ebn);
+       if (ret < 0)
+               goto out;
+       q = make_message(fmt2, ret + 1, ebn);
+       ret = real_query(q);
+       free(q);
+out:
+       if (ebn)
+               free(ebn);
+       return ret;
+}
+/* If called as child, mmd_lock must be held */
+static void update_mmd(char *info)
+{
+       PARA_DEBUG_LOG("%s", "updating shared memory area\n");
+       strncpy(mmd->dbinfo, info, MMD_INFO_SIZE - 1);
+       mmd->dbinfo[MMD_INFO_SIZE - 1] = '\0';
+}
+
+static void update_audio_file_server_handler(char *name)
+{
+       char *info;
+       info = get_dbinfo(name);
+       update_mmd(info);
+       free(info);
+       update_audio_file(name);
+}
+
+static int com_us(__unused int fd, int argc, char *argv[])
+{
+       if (argc != 1)
+               return -E_MYSQL_SYNTAX;
+       return update_audio_file(argv[1]);
+}
+
+static void refresh_mmd_dbinfo(void)
+{
+       char *name = get_current_audio_file();
+       char *info;
+
+       if (!name)
+               return;
+       info = get_dbinfo(name);
+       free(name);
+       mmd_lock();
+       update_mmd(info);
+       mmd_unlock();
+       free(info);
+}
+
+/* select previous/next stream */
+static int com_ps(__unused int fd, int argc, char *argv[])
+{
+       char *query, *stream = get_current_stream();
+       void *result = get_result("select name from streams");
+       MYSQL_ROW row;
+       int match = -1, ret, i;
+       unsigned int num_rows;
+
+       if (argc)
+               return -E_MYSQL_SYNTAX;
+       ret = -E_NORESULT;
+       if (!result)
+               goto out;
+       num_rows = mysql_num_rows(result);
+       ret = -E_EMPTY_RESULT;
+       if (num_rows < 2)
+               goto out;
+       ret = -E_NOROW;
+       for (i = 0; i < num_rows; i++) {
+               row = mysql_fetch_row(result);
+               if (!row || !row[0])
+                       goto out;
+               if (!strcmp(row[0], "current_stream"))
+                       continue;
+               if (!strcmp(row[0], stream)) {
+                       match = i;
+                       break;
+               }
+       }
+       ret = -E_NO_STREAM;
+       if (match < 0)
+               goto out;
+       if (!strcmp(argv[0], "ps"))
+               i = match > 0? match - 1 : num_rows - 1;
+       else
+               i = match < num_rows - 1? match + 1 : 0;
+       ret = -E_NOROW;
+        mysql_data_seek(result, i);
+       row = mysql_fetch_row(result);
+       if (!row || !row[0])
+               goto out;
+       if (!strcmp(row[0], "current_stream")) {
+               if (!strcmp(argv[0], "ps")) {
+                       i = match - 2;
+                       i = i < 0? i + num_rows : i;
+               } else {
+                       i = match + 2;
+                       i = i > num_rows - 1? i - num_rows : i;
+               }
+               mysql_data_seek(result, i);
+               row = mysql_fetch_row(result);
+               if (!row || !row[0])
+                       goto out;
+       }
+       query = make_message("update streams set def='%s' where name = "
+               "'current_stream'", row[0]);
+       ret = real_query(query);
+       free(query);
+       refresh_mmd_dbinfo();
+out:
+       free(stream);
+       if (result)
+               mysql_free_result(result);
+       return ret;
+}
+
+/* streams */
+static int com_streams(int fd, int argc, __unused char *argv[])
+{
+       unsigned int num_rows;
+       int i, ret = -E_NORESULT;
+       void *result;
+       MYSQL_ROW row;
+
+       if (argc && strcmp(argv[1], "current_stream"))
+               return -E_MYSQL_SYNTAX;
+       if (argc) {
+               char *cs = get_current_stream();
+               ret = send_va_buffer(fd, "%s\n", cs);
+               free(cs);
+               return ret;
+       }
+       result = get_result("select name from streams");
+       if (!result)
+               goto out;
+       num_rows = mysql_num_rows(result);
+       ret = 1;
+       if (!num_rows)
+               goto out;
+       ret = -E_NOROW;
+       for (i = 0; i < num_rows; i++) {
+               row = mysql_fetch_row(result);
+               if (!row || !row[0])
+                       goto out;
+               if (strcmp(row[0], "current_stream"))
+                       send_va_buffer(fd, "%s\n", row[0]);
+       }
+       ret = 1;
+out:
+       if (result)
+               mysql_free_result(result);
+       return ret;
+}
+
+/* query stream definition */
+static int com_strq(int fd, int argc, char *argv[])
+{
+       MYSQL_ROW row;
+       char *query, *name;
+       void *result;
+       int ret;
+
+       if (argc < 1) {
+               ret = -E_GET_STREAM;
+               name = get_current_stream();
+       } else {
+               ret = -E_ESCAPE;
+               name = escaped_basename(argv[1]);
+       }
+       if (!name)
+               return ret;
+       ret = -E_NORESULT;
+       query = make_message("select def from streams where name='%s'", name);
+       free(name);
+       result = get_result(query);
+       free(query);
+       if (!result)
+               goto out;
+       ret = -E_NOROW;
+       row = mysql_fetch_row(result);
+       if (!row || !row[0])
+               goto out;
+       /* no '\n' needed */
+       ret = send_buffer(fd, row[0]);
+out:
+       if (result)
+               mysql_free_result(result);
+       return ret;
+}
+
+/* change stream / change stream and play */
+static int com_cs(int fd, int argc, char *argv[])
+{
+       int ret, stream_change;
+       char *query;
+       char *old_stream = get_current_stream();
+       int csp = !strcmp(argv[0], "csp");
+
+       if (!argc) {
+               ret = -E_MYSQL_SYNTAX;
+               if (csp)
+                       goto out;
+               ret = send_va_buffer(fd, "%s\n", old_stream);
+               goto out;
+       }
+       ret = -E_GET_QUERY;
+       query = get_query(argv[1], NULL, 0); /* test if stream is valid */
+       if (!query)
+               goto out;
+       free(query);
+       /* stream is ok */
+       stream_change = strcmp(argv[1], old_stream);
+       if (stream_change) {
+               ret = change_stream(argv[1]);
+               if (ret < 0)
+                       goto out;
+               refresh_mmd_dbinfo();
+       }
+       if (csp) {
+               mmd_lock();
+               mmd->new_afs_status_flags |= AFS_PLAYING;
+               if (stream_change)
+                       mmd->new_afs_status_flags |= AFS_NEXT;
+               mmd_unlock();
+       }
+       ret = 1;
+out:
+       free(old_stream);
+       return ret;
+}
+
+/*
+ * sl/skip
+ */
+static int com_sl(int fd, int argc, char *argv[])
+{
+       void *result = NULL;
+       MYSQL_ROW row;
+       int ret, i, skip = !strcmp(argv[0], "skip");
+       char *query, *stream, *tmp;
+       unsigned int num_rows, num;
+
+       if (argc < 1)
+               return -E_MYSQL_SYNTAX;
+       num = atoi(argv[1]);
+       if (!num)
+               return -E_MYSQL_SYNTAX;
+       stream = (argc == 1)?  get_current_stream() : para_strdup(argv[2]);
+       tmp = get_query(stream, NULL, 0);
+       query = make_message("%s limit %d", tmp, num);
+       free(tmp);
+       ret = -E_GET_QUERY;
+       free(stream);
+       if (!query)
+               goto out;
+       ret = -E_NORESULT;
+       result = get_result(query);
+       free(query);
+       if (!result)
+               goto out;
+       ret = -E_EMPTY_RESULT;
+       num_rows = mysql_num_rows(result);
+       if (!num_rows)
+               goto out;
+       for (i = 0; i < num_rows && i < num; i++) {
+               row = mysql_fetch_row(result);
+               if (skip) {
+                       send_va_buffer(fd, "Skipping %s\n", row[0]);
+                       update_audio_file(row[0]);
+               } else
+                       send_va_buffer(fd, "%s\n", row[0]? row[0]: "BUG");
+       }
+       ret = 1;
+out:
+       if (result)
+               mysql_free_result(result);
+       return ret;
+}
+
+/*
+ * update attributes of name
+ */
+static int update_atts(int fd, char *name, char *atts)
+{
+       int ret;
+       char *ebn, *q, *old, *new = NULL;
+
+       if (!mysql_ptr)
+               return -E_NOTCONN;
+       ebn = escaped_basename(name);
+       if (!ebn)
+               return -E_ESCAPE;
+       q = make_message("update data set %s where name = '%s'", atts, ebn);
+       old = get_atts(ebn, 0);
+       send_va_buffer(fd, "old: %s\n", old);
+       free(old);
+       ret = real_query(q);
+       free(q);
+       if (ret < 0)
+               goto out;
+       new = get_atts(ebn, 0);
+       ret = send_va_buffer(fd, "new: %s\n", new);
+       free(new);
+out:
+       free(ebn);
+       return ret;
+}
+
+/*
+ * set attributes
+ */
+static int com_sa(int fd, int argc, char *argv[])
+{
+       int i, ret;
+       char *atts = NULL, *name;
+
+       if (argc < 1)
+               return -E_MYSQL_SYNTAX;
+       for (i = 1; i <= argc; i++) {
+               int unset = 0;
+               char *tmp, *p =argv[i];
+               int len = strlen(p);
+
+               if (!len)
+                       continue;
+               switch (p[len - 1]) {
+                       case '+':
+                               unset = 0;
+                               break;
+                       case '-':
+                               unset = 1;
+                               break;
+                       default:
+                               goto no_more_atts;
+               }
+               p[len - 1] = '\0';
+               tmp = make_message("%s%s='%s'", atts? "," : "", p,
+                       unset? "0" : "1");
+               atts = para_strcat(atts, tmp);
+               free(tmp);
+       }
+no_more_atts:
+       if (!atts)
+               return -E_NOATTS;
+       if (i > argc) { /* no name given, use current af */
+               ret = -E_GET_AUDIO_FILE;
+               if (!(name = get_current_audio_file()))
+                       goto out;
+               ret = update_atts(fd, name, atts);
+               free(name);
+       } else {
+               ret = 1;
+               for (; argv[i] && ret >= 0; i++)
+                       ret = update_atts(fd, argv[i], atts);
+       }
+       refresh_mmd_dbinfo();
+out:
+       return ret;
+}
+
+/*
+ * copy attributes
+ */
+static int com_cam(int fd, int argc, char *argv[])
+{
+       char *name = NULL, *meta = NULL, *atts = NULL;
+       int i, ret;
+
+       if (argc < 2)
+               return -E_MYSQL_SYNTAX;
+       if (!(name = escaped_basename(argv[1])))
+               return -E_ESCAPE;
+       ret = -E_NOATTS;
+       if (!(atts = get_atts(name, 1)))
+               goto out;
+       ret = -E_META;
+       if (!(meta = get_meta(name, 0)))
+               goto out;
+       for (i = 2; i <= argc; i++) {
+               char *ebn, *q;
+               ret = -E_ESCAPE;
+               if (!(ebn = escaped_basename(argv[i])))
+                       goto out;
+               ret = send_va_buffer(fd, "updating %s\n", ebn);
+               if (ret < 0) {
+                       free(ebn);
+                       goto out;
+               }
+               q = make_message("update data set %s where name = '%s'",
+                       meta, ebn);
+               if ((ret = update_atts(fd, ebn, atts)) >= 0)
+                       ret = real_query(q);
+               free(ebn);
+               free(q);
+               if (ret < 0)
+                       goto out;
+       }
+       ret = 1;
+out:
+       if (name)
+               free(name);
+       if (meta)
+               free(meta);
+       if (atts)
+               free(atts);
+       return ret;
+}
+
+/*
+ * verify / clean
+ */
+static int com_vrfy(int fd, int argc, __unused char *argv[])
+{
+       char *query;
+       int ret, vrfy_mode = strcmp(argv[0], "clean");
+       void *result = NULL;
+       unsigned int num_rows;
+       MYSQL_ROW row;
+       char *escaped_name;
+
+       if (argc)
+               return -E_MYSQL_SYNTAX;
+       ret = -E_NORESULT;
+       result = get_result("select data.name from data left join dir on "
+               "dir.name = data.name where dir.name is NULL");
+       if (!result)
+               goto out;
+       num_rows = mysql_num_rows(result);
+       if (!num_rows) {
+               ret = send_buffer(fd, "No invalid entries\n");
+               goto out;
+       }
+       if (vrfy_mode) {
+               send_va_buffer(fd, "found %i invalid entr%s\n", num_rows,
+                       num_rows == 1? "y" : "ies");
+               ret = print_results(fd, result, 0, 0, num_rows - 1, 0);
+               goto out;
+       }
+       while ((row = mysql_fetch_row(result))) {
+               ret = -E_NOROW;
+               if (!row[0])
+                       goto out;
+               ret = -E_ESCAPE;
+               escaped_name = escape_str(row[0]);
+               if (!escaped_name)
+                       goto out;
+               send_va_buffer(fd, "deleting %s\n", escaped_name);
+               query = make_message("delete from data where name = '%s'",
+                       escaped_name);
+               ret = real_query(query);
+               free(query);
+               if (ret < 0)
+                       goto out;
+       }
+
+out:
+       if (result)
+               mysql_free_result(result);
+       return ret;
+}
+
+static FILE *out_file;
+
+static int mysql_write_tmp_file(const char *dir, const char *name)
+{
+       int ret = -E_TMPFILE;
+       char *msg = make_message("%s\t%s\n", dir, name);
+
+       if (fputs(msg, out_file) != EOF)
+               ret = 1;
+       free(msg);
+       return ret;
+}
+
+/*
+ * update database
+ */
+static int com_upd(int fd, int argc, __unused char *argv[])
+{
+       char *tempname = NULL, *query = NULL;
+       int ret, out_fd = -1, num = 0;
+       void *result = NULL;
+       unsigned int num_rows;
+       MYSQL_ROW row;
+
+       if (argc)
+               return -E_MYSQL_SYNTAX;
+       out_file = NULL;
+       tempname = para_strdup("/tmp/mysql.tmp.XXXXXX");
+       ret = para_mkstemp(tempname, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+       if (ret < 0)
+               goto out;
+       out_fd = ret;
+       out_file = fdopen(out_fd, "w");
+       if (!out_file) {
+               close(out_fd);
+               goto out;
+       }
+       if (find_audio_files(conf.mysql_audio_file_dir_arg, mysql_write_tmp_file) < 0)
+               goto out;
+       num = ftell(out_file);
+       /*
+        * we have to make sure the file hit the disk before we call
+        * real_query
+        */
+       fclose(out_file);
+       out_file = NULL;
+       PARA_DEBUG_LOG("wrote tempfile %s (%d bytes)\n", tempname, num);
+       if (!num)
+               goto out;
+       if ((ret = real_query("delete from dir")) < 0)
+               goto out;
+       query = make_message("load data infile '%s' into table dir "
+               "fields terminated by '\t' lines terminated by '\n' "
+               "(dir, name)", tempname);
+       ret = real_query(query);
+       free(query);
+       if (ret < 0)
+               goto out;
+       result = get_result("select dir.name from dir left join data on "
+               "data.name = dir.name where data.name is NULL");
+       ret = -E_NORESULT;
+       if (!result)
+               goto out;
+       num_rows = mysql_num_rows(result);
+       if (!num_rows) {
+               ret = send_buffer(fd, "no new entries\n");
+               goto out;
+       }
+       while ((row = mysql_fetch_row(result))) {
+               ret = -E_NOROW;
+               if (!row[0])
+                       goto out;
+               send_va_buffer(fd, "new entry: %s\n", row[0]);
+               query = make_message("insert into data (name, pic_id) values "
+                       "('%s','%s')", row[0], "1");
+               ret = real_query(query);
+               free(query);
+               if (ret < 0)
+                       goto out;
+       }
+       ret = 1;
+out:
+       if (out_fd >= 0)
+               unlink(tempname);
+       free(tempname);
+       if (out_file)
+               fclose(out_file);
+       if (result)
+               mysql_free_result(result);
+       return ret;
+}
+
+static char **server_get_audio_file_list(unsigned int num)
+{
+       char **list = para_malloc((num + 1) * sizeof(char *));
+       char *tmp, *query, *stream = get_current_stream();
+       void *result = NULL;
+       unsigned int num_rows;
+       int i = 0;
+       MYSQL_ROW row;
+
+       tmp = get_query(stream, NULL, 1);
+       free(stream);
+       query = make_message("%s limit %d", tmp, num);
+       free(tmp);
+       if (!query)
+               goto err_out;
+       result = get_result(query);
+       if (!result)
+               goto err_out;
+       num_rows = mysql_num_rows(result);
+       if (!num_rows)
+               goto err_out;
+       for (i = 0; i < num_rows && i < num; i++) {
+               row = mysql_fetch_row(result);
+               if (!row || !row[0])
+                       goto err_out;
+               list[i] = para_strdup(row[0]);
+       }
+       list[i] = NULL;
+       goto success;
+err_out:
+       while (i > 0) {
+               i--;
+               free(list[i]);
+       }
+       free(list);
+       list = NULL;
+success:
+       if (query)
+               free(query);
+       if (result)
+               mysql_free_result(result);
+       return list;
+}
+
+/*
+ * connect to mysql server, return mysql pointer on success, -E_NOTCONN
+ * on errors. Called from parent on startup and also from com_cdb().
+ */
+static int init_mysql_server(void)
+{
+       char *u = conf.mysql_user_arg? conf.mysql_user_arg : para_logname();
+
+       mysql_ptr = mysql_init(NULL);
+       if (!mysql_ptr) {
+               PARA_CRIT_LOG("%s", "mysql init error\n");
+               return -E_NOTCONN;
+       }
+       PARA_DEBUG_LOG("connecting: %s@%s:%d\n", u, conf.mysql_host_arg,
+               conf.mysql_port_arg);
+       if (!conf.mysql_user_arg)
+               free(u);
+       /*
+         * If host is NULL a connection to the local host is assumed,
+         * If user is NULL, the current user is assumed
+        */
+       if (!(mysql_ptr = mysql_real_connect(mysql_ptr,
+                       conf.mysql_host_arg,
+                       conf.mysql_user_arg,
+                       conf.mysql_passwd_arg,
+                       conf.mysql_database_arg,
+                       conf.mysql_port_arg, NULL, 0))) {
+               PARA_CRIT_LOG("%s", "connect error\n");
+               return -E_NOTCONN;
+       }
+       PARA_INFO_LOG("%s", "success\n");
+       return 1;
+}
+
+/* mmd lock must be held */
+static void write_msg2mmd(int success)
+{
+       sprintf(mmd->dbinfo, "dbinfo1:%s\ndbinfo2:mysql-%s\ndbinfo3:\n",
+               success < 0? PARA_STRERROR(-success) :
+                       "successfully connected to mysql server",
+               success < 0? "" : mysql_get_server_info(mysql_ptr));
+}
+
+/* create database */
+static int com_cdb(int fd, int argc, char *argv[])
+{
+       char *query, *name;
+       int ret;
+
+       if (argc < 1)
+               name = "paraslash";
+       else {
+               ret = -E_NAMETOOLONG;
+               name = argv[1];
+               if (strlen(name) > MAXLINE)
+                       goto out;
+       }
+       if (mysql_ptr) {
+               PARA_INFO_LOG("%s", "closing database\n");
+               mysql_close(mysql_ptr);
+       }
+       /* dont use any database */
+       conf.mysql_database_arg = NULL; /* leak? */
+       ret = -E_MYSQL_INIT;
+       if (init_mysql_server() < 0 || !mysql_ptr)
+               goto out;
+       query = make_message("create database %s", name);
+       ret = real_query(query);
+       free(query);
+       if (ret < 0)
+               goto out;
+       /* reconnect with database just created */
+       mysql_close(mysql_ptr);
+       conf.mysql_database_arg = para_strdup(name);
+       ret = -E_MYSQL_INIT;
+       if (init_mysql_server() < 0 || !mysql_ptr)
+               goto out;
+       mmd_lock();
+       write_msg2mmd(1);
+       mmd_unlock();
+       ret = -E_QFAILED;
+       if (real_query("create table data (name varchar(255) binary not null "
+                       "primary key, "
+                       "lastplayed datetime not null default "
+                               "'1970-01-01', "
+                       "numplayed int not null default 0, "
+                       "pic_id bigint unsigned not null default 1)") < 0)
+               goto out;
+       if (real_query("create table dir (name varchar(255) binary not null "
+                       "primary key, dir varchar(255) default null)") < 0)
+               goto out;
+       if (real_query("create table pics ("
+                       "id bigint(20) unsigned not null primary key "
+                       "auto_increment, "
+                       "name varchar(255) binary not null, "
+                       "pic mediumblob not null)") < 0)
+               goto out;
+       if (real_query("create table streams ("
+                       "name varchar(255) binary not null primary key, "
+                       "def blob not null)") < 0)
+               goto out;
+       if (real_query("insert into streams (name, def) values "
+                       "('current_stream', '(none)')") < 0)
+               goto out;
+       ret = send_va_buffer(fd, "successfully created database %s\n", name);
+out:
+       return ret;
+}
+
+static void shutdown_connection(void)
+{
+       if (mysql_ptr) {
+               PARA_NOTICE_LOG("%s", "shutting down mysql connection\n");
+               mysql_close(mysql_ptr);
+               mysql_ptr = NULL;
+       }
+}
+
+/**
+ * the init function of the mysql-based database tool
+ *
+ * Check the command line options and initialize all function pointers of \a db.
+ * Connect to the mysql server and initialize the dbinfo string.
+ *
+ * \sa struct dbtool, misc_meta_data::dbinfo, dopey.c
+ */
+int mysql_dbtool_init(struct dbtool *db)
+{
+       int ret;
+
+       if (!conf.mysql_passwd_given)
+               return -E_NO_MYSQL_PASSWD;
+       if (!conf.mysql_audio_file_dir_given)
+               return -E_NO_AF_DIR;
+       db->name = "mysql";
+       db->cmd_list = cmds;
+       db->get_audio_file_list = server_get_audio_file_list;
+       db->update_audio_file = update_audio_file_server_handler;
+       db->shutdown = shutdown_connection;
+       ret = init_mysql_server();
+       if (ret < 0)
+               PARA_WARNING_LOG("%s\n", PARA_STRERROR(-ret));
+       write_msg2mmd(ret);
+       return 1;       /* return success even if connect failed to give the
+                        * user the chance to exec com_cdb
+                        */
+}
diff --git a/net.c b/net.c
new file mode 100644 (file)
index 0000000..59b8616
--- /dev/null
+++ b/net.c
@@ -0,0 +1,504 @@
+/*
+ * 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 net.c networking-related helper functions */
+
+#include "para.h"
+#include "net.h"
+#include "gcc-compat.h"
+#include <netdb.h>
+#include "error.h"
+#include "string.h"
+
+extern void (*crypt_function_recv)(unsigned long len, const unsigned char *indata, unsigned char *outdata);
+extern void (*crypt_function_send)(unsigned long len, const unsigned char *indata, unsigned char *outdata);
+
+/**
+ * initialize a struct sockaddr_in
+ * @param addr A pointer to the struct to be initialized
+ * @param port The port number to use
+ * @param he The address to use
+ *
+ * If \a he is null (server mode), \a addr->sin_addr is initialized with \p INADDR_ANY.
+ * Otherwise, the address given by \a he is copied to addr.
+ */
+void init_sockaddr(struct sockaddr_in *addr, int port, const struct hostent *he)
+{
+       /* host byte order */
+       addr->sin_family = AF_INET;
+       /* short, network byte order */
+       addr->sin_port = htons(port);
+       if (he)
+               addr->sin_addr = *((struct in_addr *)he->h_addr);
+       else
+               addr->sin_addr.s_addr = INADDR_ANY;
+       /* zero the rest of the struct */
+       memset(&addr->sin_zero, '\0', 8);
+}
+
+/*
+ * send out a buffer, resend on short writes
+ * @param fd the file descriptor
+ * @param buf The buffer to be sent
+ * @len The length of \a buf
+ *
+ * Due to circumstances beyond your control, the kernel might not send all the
+ * data out in one chunk, and now, my friend, it's up to us to get the data out
+ * there (Beej's Guide to Network Programming).
+ */
+static int sendall(int fd, const char *buf, size_t *len)
+{
+       int total = 0;        /* how many bytes we've sent */
+       int bytesleft = *len; /* how many we have left to send */
+       int n = -1;
+
+       while (total < *len) {
+               n = send(fd, buf + total, bytesleft, 0);
+               if (n == -1)
+                       break;
+               total += n;
+               bytesleft -= n;
+               if (total < *len)
+                       PARA_DEBUG_LOG("short write (%d byte(s) left)",
+                               *len - total);
+       }
+       *len = total; /* return number actually sent here */
+       return n == -1? -E_SEND : 1; /* return 1 on success */
+}
+
+/**
+ * encrypt and send buffer
+ * @param fd:  the file descriptor
+ * @param buf the buffer to be encrypted and sent
+ * @param len the length of \a buf
+ *
+ * Check if encrytion is available. If yes, encrypt the given buffer.  Send out
+ * the buffer, encrypted or not, and try to resend the remaing part in case of
+ * short writes.
+ *
+ * @return Positive on success, \p -E_SEND on errors.
+ */
+int send_bin_buffer(int fd, const char *buf, size_t len)
+{
+       int ret;
+
+       if (!len)
+               PARA_CRIT_LOG("%s", "len == 0\n");
+       if (crypt_function_send) {
+               unsigned char *outbuf = para_malloc(len);
+               crypt_function_send(len, (unsigned char *)buf, outbuf);
+               ret = sendall(fd, (char *)outbuf, &len);
+               free(outbuf);
+       } else
+               ret = sendall(fd, buf, &len);
+       return ret;
+}
+
+/**
+ * encrypt and send null terminated buffer.
+ * @param fd the file descriptor
+ * @param buf the null-terminated buffer to be send
+ *
+ * This is equivalent to send_bin_buffer(fd, buf, strlen(buf)).
+ *
+ * @return Positive on success, \p -E_SEND on errors.
+ */
+int send_buffer(int fd, const char *buf)
+{
+       return send_bin_buffer(fd, buf, strlen(buf));
+}
+
+
+/**
+ * send and encrypt a buffer given by a format string
+ * @param fd the file descriptor
+ * @param fmt a format string
+ *
+ * @return Positive on success, \p -E_SEND on errors.
+ */
+__printf_2_3 int send_va_buffer(int fd, char *fmt, ...)
+{
+       char *msg;
+       int ret;
+
+       PARA_VSPRINTF(fmt, msg);
+       ret = send_buffer(fd, msg);
+       free(msg);
+       return ret;
+}
+
+/**
+ * receive and decrypt.
+ *
+ * @param fd the file descriptor
+ * @param buf the buffer to write the decrypted data to
+ * @param size the size of @param buf
+ *
+ * Receive at most \a size bytes from filedescriptor fd. If encrytion is
+ * available, decrypt the received buffer.
+ *
+ * @return the number of bytes received on success. On receive errors, -E_RECV
+ * is returned. On crypt errors, the corresponding crypt error number is
+ * returned.
+ * \sa recv(2)
+ */
+__must_check int recv_bin_buffer(int fd, char *buf, ssize_t size)
+{
+       int n;
+
+       if (crypt_function_recv) {
+               unsigned char *tmp = para_malloc(size);
+               n = recv(fd, tmp, size, 0);
+               if (n > 0)
+                       crypt_function_recv(n, tmp, (unsigned char *)buf);
+               free(tmp);
+       } else
+               n = recv(fd, buf, size, 0);
+       if (n == -1)
+               n = -E_RECV;
+       return n;
+}
+
+/**
+ * receive, decrypt and write terminating NULL byte
+ *
+ * @param fd the file descriptor
+ * @param buf the buffer to write the decrypted data to
+ * @param size the size of \a buf
+ *
+ * Read and decrypt at most size - 1 bytes from file descriptor \a fd and write
+ * a NULL byte at the end of the received data.
+ *
+*/
+int recv_buffer(int fd, char *buf, ssize_t size)
+{
+       int n;
+
+       if ((n = recv_bin_buffer(fd, buf, size - 1)) >= 0)
+               buf[n] = '\0';
+       return n;
+}
+
+/**
+ * wrapper around gethostbyname
+ *
+ * @param host hostname or IPv4 address
+ * \return The hostent structure or a NULL pointer if an error occurs
+ * \sa gethostbyname(2)
+ */
+struct hostent *get_host_info(char *host)
+{
+       PARA_INFO_LOG("getting host info of %s\n", host);
+       /* FIXME: gethostbyname() is obsolete */
+       return gethostbyname(host);
+}
+
+/**
+ * a wrapper around socket(2)
+ *
+ * Create an IPv4 socket for sequenced, reliable, two-way, connection-based
+ * byte streams.
+ *
+ * @return The socket fd on success, -E_SOCKET on errors.
+ * \sa socket(2)
+ */
+int get_socket(void)
+{
+       int socket_fd;
+
+       if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
+               return -E_SOCKET;
+       return socket_fd;
+}
+
+/**
+ * a wrapper around connect(2)
+ *
+ * @param fd the file descriptor
+ * @param their_addr the address to connect
+ *
+ * @return \p -E_CONNECT on errors, 1 on success
+ * \sa connect(2)
+ */
+int para_connect(int fd, struct sockaddr_in *their_addr)
+{
+       int ret;
+
+       if ((ret = connect(fd, (struct sockaddr *)their_addr,
+                       sizeof(struct sockaddr))) == -1)
+               return -E_CONNECT;
+       return 1;
+}
+
+/**
+ * paraslash's wrapper around the accept system call
+ *
+ * @param fd the listening socket
+ * @param addr structure which is filled in with the address of the peer socket
+ * @param size should contain the size of the structure pointed to by \a addr
+ *
+ * \sa accept(2).
+ */
+int para_accept(int fd, void *addr, size_t size)
+{
+       int new_fd;
+
+       new_fd = accept(fd, (struct sockaddr *) addr, &size);
+       return new_fd == -1? -E_ACCEPT : new_fd;
+}
+
+static int setserversockopts(int socket_fd)
+{
+       int yes = 1;
+
+       if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &yes,
+                       sizeof(int)) == -1)
+               return -E_SETSOCKOPT;
+       return 1;
+}
+
+static int do_bind(int socket_fd, struct sockaddr_in *my_addr)
+{
+       if (bind(socket_fd, (struct sockaddr *)my_addr,
+               sizeof(struct sockaddr)) == -1)
+               return -E_BIND;
+       return 1;
+}
+
+/**
+ * prepare a structure for \p AF_UNIX socket addresses
+ *
+ * \param u pointer to the struct to be prepared
+ * \param name the socket pathname
+ *
+ * This just copies \a name to the sun_path component of \a u.
+ *
+ * \return Positive on success, \p -E_NAME_TOO_LONG if \a name is longer
+ * than \p UNIX_PATH_MAX.
+ */
+int init_unix_addr(struct sockaddr_un *u, const char *name)
+{
+       if (strlen(name) >= UNIX_PATH_MAX)
+               return -E_NAME_TOO_LONG;
+       memset(u->sun_path, 0, UNIX_PATH_MAX);
+       u->sun_family = PF_UNIX;
+       strcpy(u->sun_path, name);
+       return 1;
+}
+
+/**
+ * prepare, create, and bind and socket for local communication
+ *
+ * \param name the socket pathname
+ * \param unix_addr pointer to the \p AF_UNIX socket structure
+ * \param mode the desired mode of the socket
+ *
+ * This functions creates a local socket for sequenced, reliable,
+ * two-way, connection-based byte streams.
+ * \sa socket(2)
+ * \sa bind(2)
+ * \sa chmod(2)
+ */
+int create_pf_socket(const char *name, struct sockaddr_un *unix_addr, int mode)
+{
+       int fd, ret;
+
+       fd = socket(PF_UNIX, SOCK_STREAM, 0);
+       if (fd < 0)
+               return -E_SOCKET;
+//     unlink(name);
+       ret = init_unix_addr(unix_addr, name);
+       if (ret < 0)
+               return ret;
+       if (bind(fd, (struct sockaddr *) unix_addr, UNIX_PATH_MAX) < 0)
+               return -E_BIND;
+       if (chmod(name, mode) < 0)
+               return -E_CHMOD;
+       return fd;
+}
+
+/**
+ * send NULL terminated buffer and Unix credentials of the current process
+ *
+ * \param sock the socket file descriptor
+ * \param buf the buffer to be sent
+ *
+ * \return On success, this call returns the number of characters sent.  On
+ * error, \p -E_SENDMSG ist returned.
+ * \sa  okir's Black Hats Manual
+ * \sa sendmsg(2)
+ */
+ssize_t send_cred_buffer(int sock, char *buf)
+{
+       char control[sizeof(struct cmsghdr) + 10];
+       struct msghdr msg;
+       struct cmsghdr *cmsg;
+       static struct iovec iov;
+       struct ucred c;
+       int ret;
+
+       /* Response data */
+       iov.iov_base = buf;
+       iov.iov_len  = strlen(buf);
+       c.pid = getpid();
+       c.uid = getuid();
+       c.gid = getgid();
+       /* compose the message */
+       memset(&msg, 0, sizeof(msg));
+       msg.msg_iov = &iov;
+       msg.msg_iovlen = 1;
+       msg.msg_control = control;
+       msg.msg_controllen = sizeof(control);
+       /* attach the ucred struct */
+       cmsg = CMSG_FIRSTHDR(&msg);
+       cmsg->cmsg_level = SOL_SOCKET;
+       cmsg->cmsg_type = SCM_CREDENTIALS;
+       cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred));
+       *(struct ucred *)CMSG_DATA(cmsg) = c;
+       msg.msg_controllen = cmsg->cmsg_len;
+       ret = sendmsg(sock, &msg, 0);
+       if (ret  < 0)
+               ret = -E_SENDMSG;
+       return ret;
+}
+
+static void dispose_fds(int *fds, int num)
+{
+       int i;
+
+       for (i = 0; i < num; i++)
+               close(fds[i]);
+}
+
+/**
+ * receive a buffer and the Unix credentials of the sending process
+ *
+ * \param fd the socket file descriptor
+ * \param buf the buffer to store the message
+ * \param size the size of \a buffer
+ * \param cred the credentials are returned here
+ *
+ * \sa okir's Black Hats Manual
+ * \sa recvmsg(2)
+ */
+int recv_cred_buffer(int fd, char *buf, size_t size, struct ucred *cred)
+{
+       char control[255];
+       struct msghdr msg;
+       struct cmsghdr *cmsg;
+       struct iovec iov;
+       int result;
+       int yes = 1;
+
+       setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &yes, sizeof(int));
+       memset(&msg, 0, sizeof(msg));
+       memset(buf, 0, size);
+       iov.iov_base = buf;
+       iov.iov_len = size;
+       msg.msg_iov = &iov;
+       msg.msg_iovlen = 1;
+       msg.msg_control = control;
+       msg.msg_controllen = sizeof(control);
+       if (recvmsg(fd, &msg, 0) < 0)
+               return -E_RECVMSG;
+       result = -SCM_CREDENTIALS;
+       cmsg = CMSG_FIRSTHDR(&msg);
+       while (cmsg) {
+               if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type
+                               == SCM_CREDENTIALS) {
+                       memcpy(cred, CMSG_DATA(cmsg), sizeof(struct ucred));
+                       result = iov.iov_len;
+               } else
+                       if (cmsg->cmsg_level == SOL_SOCKET
+                                       && cmsg->cmsg_type == SCM_RIGHTS) {
+                               dispose_fds((int *) CMSG_DATA(cmsg),
+                                       (cmsg->cmsg_len - CMSG_LEN(0))
+                                       / sizeof(int));
+                       }
+               cmsg = CMSG_NXTHDR(&msg, cmsg);
+       }
+       return result;
+}
+
+/** how many pending connections queue will hold */
+#define BACKLOG 10
+
+/**
+ * create a socket, bind it and listen
+ * \param port the tcp port to listen on
+ *
+ * \return The file descriptor of the created socket, negative
+ * on errors.
+ *
+ * \sa  get_socket()
+ * \sa setsockopt(2)
+ * \sa bind(2)
+ * \sa listen(2)
+ */
+int init_tcp_socket(int port)
+{
+       int sockfd, ret;
+       struct sockaddr_in my_addr;
+
+       if ((sockfd = get_socket()) < 0)
+               return sockfd;
+       ret = setserversockopts(sockfd);
+       if (ret < 0)
+               return ret;
+       init_sockaddr(&my_addr, port, NULL);
+       ret = do_bind(sockfd, &my_addr);
+       if (ret < 0)
+               return ret;
+       if (listen(sockfd, BACKLOG) == -1)
+               return -E_LISTEN;
+       PARA_INFO_LOG("listening on port %d, fd %d\n", port, sockfd);
+       return sockfd;
+}
+
+/**
+ * receive a buffer and check for a pattern
+ *
+ * \param fd the file descriptor to receive from
+ * \param pattern the expected pattern
+ * \param bufsize the size of the internal buffer
+ *
+ * \return Positive if \a pattern was received, negative otherwise.
+ *
+ * This function creates a buffer of size \a bufsize and tries
+ * to receive at most \a bufsize bytes from file descriptor \a fd.
+ * If at least \p strlen(\a pattern) bytes were received, the beginning of
+ * the received buffer is compared with \a pattern, ignoring case.
+ * \sa recv_buffer()
+ * \sa strncasecmp(3)
+ */
+int recv_pattern(int fd, const char *pattern, size_t bufsize)
+{
+       size_t len = strlen(pattern);
+       char *buf = para_malloc(bufsize + 1);
+       int ret = -E_RECV_PATTERN, n = recv_buffer(fd, buf, bufsize);
+
+       if (n < len)
+               goto out;
+       buf[n] = '\0';
+       if (strncasecmp(buf, pattern, len))
+               goto out;
+       ret = 1;
+out:
+       free(buf);
+       return ret;
+}
diff --git a/net.h b/net.h
new file mode 100644 (file)
index 0000000..ebb3983
--- /dev/null
+++ b/net.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2006 Andre Noll <maan@systemlinux.org>
+ *
+ *     This program is free software; you can redistribute it and/or modify
+ *     it under the terms of the GNU General Public License as published by
+ *     the Free Software Foundation; either version 2 of the License, or
+ *     (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU General Public License for more details.
+ *
+ *     You should have received a copy of the GNU General Public License
+ *     along with this program; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ */
+
+/** \file net.h exported symbols from net.c */
+
+/**
+ * the buffer size of the sun_path component of struct sockaddr_un
+ *
+ * While glibc doesn't define \p UNIX_PATH_MAX, it
+ * documents it has being limited to 108 bytes.
+ */
+#ifndef UNIX_PATH_MAX
+#define UNIX_PATH_MAX 108
+#endif
+
+struct hostent *get_host_info(char *);
+int get_socket(void);
+void init_sockaddr(struct sockaddr_in*, int, const struct hostent*);
+int para_connect(int, struct sockaddr_in *);
+int send_buffer(int, const char *);
+int send_bin_buffer(int, const char *, size_t);
+int send_va_buffer(int, char *, ...);
+int recv_buffer(int, char *, ssize_t);
+int recv_bin_buffer(int, char *, ssize_t);
+int para_accept(int, void *addr, size_t size);
+int create_pf_socket(const char *, struct sockaddr_un *, int mod);
+int init_unix_addr(struct sockaddr_un *, const char *);
+int recv_cred_buffer(int, char *, size_t, struct ucred *);
+ssize_t send_cred_buffer(int, char*);
+int recv_pattern(int fd, const char *pattern, size_t bufsize);
+int init_tcp_socket(int port);
+
diff --git a/ogg.c b/ogg.c
new file mode 100644 (file)
index 0000000..d3ebd46
--- /dev/null
+++ b/ogg.c
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2004-2006 Andre Noll <maan@systemlinux.org>
+ *
+ *     This program is free software; you can redistribute it and/or modify
+ *     it under the terms of the GNU General Public License as published by
+ *     the Free Software Foundation; either version 2 of the License, or
+ *     (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU General Public License for more details.
+ *
+ *     You should have received a copy of the GNU General Public License
+ *     along with this program; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ */
+/** \file ogg.c para_server's ogg vorbis audio format handler */
+
+#include <ogg/ogg.h>
+#include <vorbis/codec.h>
+#include <vorbis/vorbisfile.h>
+
+#include "server.cmdline.h"
+#include "server.h"
+#include "afs.h"
+#include "error.h"
+#include "string.h"
+
+/* must be big enough to hold header */
+#define CHUNK_SIZE 32768
+static double chunk_time = 0.25;
+
+static OggVorbis_File *oggvorbis_file;
+static FILE *infile;
+static int header_len, oggbuf_len, vi_channels;
+static char *header, *oggbuf;
+static ogg_int64_t *chunk_table, max_chunk_len;
+struct audio_format *af;
+static long vi_sampling_rate, vi_bitrate, vi_bitrate_nominal,
+       num_chunks;
+
+static int ogg_compute_header_len(void)
+{
+       int ret, len, in = fileno(infile);
+       unsigned int serial;
+       char *buf;
+       ogg_page page;
+        ogg_packet packet;
+       vorbis_comment vc;
+       vorbis_info vi;
+       ogg_stream_state *stream_in = para_malloc(sizeof(ogg_stream_state));
+       ogg_stream_state *stream_out = para_malloc(sizeof(ogg_stream_state));
+       ogg_sync_state *sync_in = para_malloc(sizeof(ogg_sync_state));
+
+       ogg_sync_init(sync_in);
+       vorbis_info_init(&vi);
+       vorbis_comment_init(&vc);
+       buf = ogg_sync_buffer(sync_in, CHUNK_SIZE);
+       len = read(in, buf, CHUNK_SIZE);
+       ret = -E_OGG_READ;
+       if (len <= 0)
+               goto err1;
+       ogg_sync_wrote(sync_in, len);
+       ret = -E_SYNC_PAGEOUT;
+       if (ogg_sync_pageout(sync_in, &page) <= 0)
+               goto err1;
+       serial = ogg_page_serialno(&page);
+       ogg_stream_init(stream_in, serial);
+       ogg_stream_init(stream_out, serial);
+       ret = ogg_stream_pagein(stream_in, &page);
+       if (ret < 0) {
+               ret = E_STREAM_PAGEIN;
+               goto err2;
+       }
+       ret = ogg_stream_packetout(stream_in, &packet);
+       if (ret != 1) {
+               ret = -E_STREAM_PACKETOUT;
+               goto err2;
+       }
+       ret = -E_VORBIS;
+       if (vorbis_synthesis_headerin(&vi, &vc, &packet) < 0) {
+               goto err2;
+       } else
+               PARA_INFO_LOG("channels: %i, rate: %li\n", vi.channels,
+                       vi.rate);
+       ogg_stream_packetin(stream_out, &packet);
+
+       ret = ogg_sync_pageout(sync_in, &page);
+       if (ret <= 0) {
+               ret = -E_SYNC_PAGEOUT;
+               goto err2;
+       }
+       ogg_stream_pagein(stream_in, &page);
+       ogg_stream_packetout(stream_in, &packet);
+       ogg_stream_packetin(stream_out, &packet);
+
+       ret = ogg_sync_pageout(sync_in, &page);
+       if (ret <= 0) {
+               ret = -E_SYNC_PAGEOUT;
+               goto err2;
+       }
+       ogg_stream_pagein(stream_in, &page);
+       ogg_stream_packetout(stream_in, &packet);
+       ogg_stream_packetin(stream_out, &packet);
+
+       header_len = 0;
+       while (ogg_stream_flush(stream_out, &page))
+               header_len += page.body_len + page.header_len;
+       ret = len;
+       PARA_INFO_LOG("header_len = %d\n", header_len);
+err2:
+       ogg_stream_destroy(stream_in);
+       ogg_stream_destroy(stream_out);
+err1:
+       ogg_sync_destroy(sync_in);
+       vorbis_info_clear(&vi);
+        vorbis_comment_clear(&vc);
+       return ret;
+}
+
+static void tunetable(void)
+{
+       int i = 1, j = -1, lp = 1;
+       while (i < num_chunks) {
+               if (chunk_table[i] == chunk_table[lp]) {
+                       i++;
+                       continue;
+               }
+               if (j < 0)
+                       tv_scale(i + 2, &af->chunk_tv, &af->eof_tv);
+               for (j = lp; j < i; j++)
+                       chunk_table[j] = chunk_table[i];
+               lp = i;
+       }
+#if 1
+       for (i = 2; i < num_chunks; i++)
+               if (chunk_table[i] != chunk_table[1])
+                       break;
+       lp = i;
+       for (i = 2; i < num_chunks - lp; i++)
+               chunk_table[i] = chunk_table[i + lp];
+#endif 
+}
+
+
+/*
+ * Alloc and fill array table of byte offsets. chunk_table[i] is the
+ * offset in the current infile at which the sample containing time i *
+ * CHUNK_TIME begins.
+ */
+static void ogg_compute_chunk_table(double time_total)
+{
+       int i, ret, num;
+       ogg_int64_t pos = 0, min = 0, old_pos;
+
+       old_pos = 0;
+       ret = 0;
+       num = time_total / chunk_time + 3;
+       PARA_DEBUG_LOG("chunk time: %g allocating %d chunk pointers\n",
+               chunk_time, num);
+       chunk_table = para_malloc(num * sizeof(ogg_int64_t));
+       chunk_table[0] = 0;
+       max_chunk_len = 0;
+       rewind(infile);
+       for (i = 1; ret == 0; i++) {
+               ogg_int64_t diff;
+               ret = ov_time_seek(oggvorbis_file, i * chunk_time);
+               if (ret)
+                       break;
+               pos = ov_raw_tell(oggvorbis_file);
+               diff = pos - old_pos;
+               max_chunk_len = MAX(max_chunk_len, diff);
+               min = (i == 1)? diff : MIN(min, diff);
+               chunk_table[i] = pos;
+               if (i < 11 || !((i - 1) % 1000)|| i > num - 11)
+                       PARA_DEBUG_LOG("chunk #%d: %g secs, pos: %lli, "
+                               "size: %lli\n", i - 1,
+                               i * chunk_time, pos, pos - old_pos);
+               old_pos = pos;
+       }
+       num_chunks = i - 1;
+       chunk_table[i] = pos;
+       tunetable();
+       PARA_INFO_LOG("%li chunks (%fs), max chunk: %lli, min chunk: %lli\n",
+               num_chunks, chunk_time, max_chunk_len, min);
+       rewind(infile);
+}
+
+static void ogg_close_audio_file(void)
+{
+       if (oggvorbis_file) {
+               PARA_DEBUG_LOG("%s", "ov_clear\n");
+               ov_clear(oggvorbis_file);
+               free(oggvorbis_file);
+               oggvorbis_file = NULL;
+       }
+       free(header);
+       header = NULL;
+       header_len = 0;
+       free(chunk_table);
+       chunk_table = NULL;
+       num_chunks = 0;
+       free(oggbuf);
+       oggbuf = NULL;
+       oggbuf_len = 0;
+}
+
+static int ogg_save_header(FILE *file, int header_len)
+{
+       int ret;
+
+       header = para_malloc(header_len);
+       rewind(file);
+       ret = read(fileno(file), header, header_len);
+       if (ret != header_len)
+               return -E_OGG_READ;
+       return 1;
+}
+
+/*
+ * Init oggvorbis file and write some tech data to given pointers.
+ */
+static int ogg_get_file_info(FILE *file, char *info_str, long unsigned *frames,
+       int *seconds)
+{
+       int ret;
+       double time_total;
+       vorbis_info *vi;
+       ogg_int64_t raw_total;
+
+       infile = file;
+       if (!file)
+               return -E_OGG_NO_FILE;
+       ret = ogg_compute_header_len();
+       if (ret < 0)
+               return ret;
+       ret = ogg_save_header(file, header_len);
+       if (ret < 0)
+               return ret;
+       rewind(file);
+       oggvorbis_file = para_malloc(sizeof(OggVorbis_File));
+       ret = ov_open(file, oggvorbis_file, NULL, 0);
+       if (ret < 0) {
+               free(oggvorbis_file);
+               free(header);
+               return -E_OGG_OPEN;
+       }
+       ret = -E_OGG_INFO;
+       vi = ov_info(oggvorbis_file, 0);
+       if (!vi)
+               goto err;
+       time_total = ov_time_total(oggvorbis_file, -1);
+       raw_total = ov_raw_total(oggvorbis_file, -1);
+       *seconds = time_total;
+       vi_sampling_rate = vi->rate;
+       vi_bitrate = ov_bitrate(oggvorbis_file, 0);
+       vi_bitrate_nominal = vi->bitrate_nominal;
+       vi_channels = vi->channels;
+       ogg_compute_chunk_table(time_total);
+       *frames = num_chunks;
+       sprintf(info_str, "audio_file_info1:%lu x %lu, %ldkHz, %d channels, %ldkbps\n"
+               "audio_file_info2: \n"
+               "audio_file_info3: \n",
+               num_chunks, (long unsigned) (chunk_time * 1000 * 1000),
+               vi_sampling_rate / 1000, vi_channels, vi_bitrate / 1000
+               );
+       rewind(file);
+       return 1;
+err:
+       ogg_close_audio_file();
+       return ret;
+}
+
+/*
+ * Simple stream reposition routine
+ */
+static int ogg_reposition_stream(long unsigned request)
+{
+       int ret;
+       long seek;
+
+       seek = chunk_table[request];
+       ret = fseek(infile, seek, SEEK_SET);
+       PARA_DEBUG_LOG("seek to %li returned %d. offset:%li\n", seek,
+               ret, ftell(infile));
+       return ret < 0? -E_OGG_REPOS : 1;
+}
+
+static ogg_int64_t get_chunk_size(long unsigned chunk_num)
+{
+       ogg_int64_t ret;
+       if (chunk_num >= num_chunks)
+               return -1;
+       ret = chunk_table[chunk_num + 1] - chunk_table[chunk_num];
+       if (!ret)
+               return ret;
+#if 0
+       PARA_DEBUG_LOG("chunk %d: %lli-%lli (%lli bytes)\n",
+               chunk_num,
+               chunk_table[chunk_num],
+               chunk_table[chunk_num + 1] - 1,
+               ret);
+#endif
+       return ret;
+}
+
+char *ogg_read_chunk(long unsigned current_chunk, ssize_t *len)
+{
+       int ret;
+       ogg_int64_t cs = get_chunk_size(current_chunk);
+       if (!cs) { /* nothing to send for this run */
+               *len = 0;
+               return oggbuf;
+       }
+       if (cs < 0) { /* eof */
+               *len = 0;
+               return NULL;
+       }
+       *len = cs;
+       if (!oggbuf || oggbuf_len < *len) {
+               PARA_INFO_LOG("increasing ogg buffer size (%d -> %u)\n",
+                       oggbuf_len, *len);
+               oggbuf = para_realloc(oggbuf, *len);
+               oggbuf_len = *len;
+       }
+       ret = read(fileno(infile), oggbuf, *len);
+       if (!ret) {
+               *len = 0;
+               return NULL;
+       }
+       if (ret < 0) {
+               *len = -E_OGG_READ;
+               return NULL;
+       }
+       if (ret != *len)
+               PARA_WARNING_LOG("short read (%d/%d)\n", ret, *len);
+       *len = ret;
+       return oggbuf;
+}
+
+static char *ogg_get_header_info(int *len)
+{
+       *len = header_len;
+       return header;
+}
+
+void ogg_init(void *p)
+{
+       af = p;
+       af->reposition_stream = ogg_reposition_stream;
+       af->get_file_info = ogg_get_file_info,
+       af->read_chunk = ogg_read_chunk;
+       af->close_audio_file = ogg_close_audio_file;
+       af->get_header_info = ogg_get_header_info;
+       af->chunk_tv.tv_sec = 0;
+       af->chunk_tv.tv_usec = 250 * 1000;
+       tv_scale(3, &af->chunk_tv, &af->eof_tv);
+}
diff --git a/oggdec.c b/oggdec.c
new file mode 100644 (file)
index 0000000..4ef16b0
--- /dev/null
+++ b/oggdec.c
@@ -0,0 +1,193 @@
+/*
+ * 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 oggdec.c paraslash's ogg vorbis decoder */
+
+#include "gcc-compat.h"
+#include "para.h"
+
+#include "oggdec_filter.cmdline.h"
+#include "list.h"
+#include "filter.h"
+#include "error.h"
+#include "string.h"
+
+#include <vorbis/vorbisfile.h>
+
+/** \cond some internal constants */
+#define BITS 16
+#define ENDIAN 0
+#define SIGN 1
+/** \endcond */
+
+/** data specific to the oggdec filter */
+struct private_oggdec_data {
+       /** describes an ogg vorbis file */
+       OggVorbis_File *vf;
+       /** the input buffer */
+       char *inbuf;
+       /** the length of \a inbuf */
+       size_t inbuf_len;
+       /** the number of bytes consumed from the input buffer */
+       size_t converted;
+};
+
+static size_t cb_read(void *buf, size_t size, size_t nmemb, void *datasource)
+{
+       struct filter_node *fn = datasource;
+       struct private_oggdec_data *pod = fn->private_data;
+       size_t ret, have = pod->inbuf_len - pod->converted;
+       char *p = pod->inbuf + pod->converted;
+
+       if (*fn->fci->eof)
+               return 0;
+//     PARA_DEBUG_LOG("pod = %p\n", pod);
+//     PARA_DEBUG_LOG("vorbis requests %d bytes, have %d\n", size * nmemb, have);
+       if (pod->inbuf_len < size) {
+               errno = EAGAIN;
+               return -1;
+       }
+       ret = MIN(nmemb, have / size) * size;
+       memcpy(buf, p, ret);
+       pod->converted += ret;
+       return ret;
+}
+
+/*
+ * cb_seek -- custom data seeking function
+ * Since we want the data source to be treated as unseekable at all
+ * times, the provided seek callback always returns -1 (failure).
+ */
+static int cb_seek(__unused void *datasource, __unused ogg_int64_t offset,
+               __unused int whence)
+{
+       return -1;
+}
+
+static int cb_close(__unused void *datasource)
+{
+       return 0;
+}
+
+static const ov_callbacks ovc = {
+       .read_func = cb_read,
+       .seek_func = cb_seek,
+       .close_func = cb_close,
+       /*
+        * The tell function need not be provided if
+        * the data IO abstraction is not seekable
+        */
+       .tell_func = NULL
+};
+
+static void ogg_open(struct filter_node *fn)
+{
+       struct private_oggdec_data *pod = para_calloc(
+               sizeof(struct private_oggdec_data));
+       struct gengetopt_args_info *conf = fn->conf;
+
+       fn->private_data = pod;
+       fn->bufsize = conf->bufsize_arg * 1024;
+       fn->buf = para_malloc(fn->bufsize);
+}
+
+static void ogg_close(struct filter_node *fn)
+{
+       struct private_oggdec_data *pod = fn->private_data;
+       if (pod->vf) {
+               PARA_DEBUG_LOG("ov_clearing %p, pod = %p\n", pod->vf, pod);
+               ov_clear(pod->vf);
+               free(pod->vf);
+               pod->vf = NULL;
+       } else
+               PARA_DEBUG_LOG("nothing to close in fc %p, pod = %p\n", pod->vf, pod);
+       free(fn->buf);
+       fn->buf = NULL;
+       free(fn->private_data);
+       fn->private_data = NULL;
+}
+
+static ssize_t ogg_convert(char *inbuffer, size_t len, struct filter_node *fn)
+{
+       ssize_t ret;
+       struct private_oggdec_data *pod = fn->private_data;
+       struct gengetopt_args_info *conf = fn->conf;
+       /* make the buffer known to the read callback cb_read() */
+       pod->inbuf = inbuffer;
+       pod->inbuf_len = len;
+       pod->converted = 0;
+
+       if (!pod->vf) {
+               int ib = 1024 * conf->initial_buffer_arg; /* initial buffer */
+               if (len <ib && !*fn->fci->eof && !fn->fci->error) {
+                       PARA_INFO_LOG("initial input buffer %d/%d, waiting for more data\n",
+                               len, ib);
+                       return 0;
+               }
+               pod->vf = para_malloc(sizeof(struct OggVorbis_File));
+               PARA_NOTICE_LOG("input buffer: %d, opening ov callbacks\n", len);
+               ret = ov_open_callbacks(fn, pod->vf,
+                       NULL, /* no initial buffer */
+                       0, /* no initial bytes */
+                       ovc); /* the ov_open_callbacks */
+               if (ret == OV_EREAD)
+                       return -E_OGGDEC_READ;
+               if (ret == OV_ENOTVORBIS)
+                       return -E_OGGDEC_NOTVORBIS;
+               if (ret == OV_EVERSION)
+                       return -E_OGGDEC_VERSION;
+               if (ret == OV_EBADHEADER)
+                       return -E_OGGDEC_BADHEADER;
+               if (ret < 0)
+                       return -E_OGGDEC_FAULT;
+               fn->fci->channels = ov_info(pod->vf, 0)->channels;
+               fn->fci->samplerate = ov_info(pod->vf, 0)->rate;
+               PARA_NOTICE_LOG("%d channels, %d Hz\n", fn->fci->channels, fn->fci->samplerate);
+       }
+again:
+       ret = ov_read(pod->vf, fn->buf + fn->loaded, fn->bufsize - fn->loaded,
+               ENDIAN, BITS / 8, SIGN, NULL);
+       if (ret == OV_HOLE || !ret) {
+               return pod->converted;
+       }
+       if (ret < 0)
+               return -E_OGGDEC_BADLINK;
+       fn->loaded += ret;
+       if (!*fn->fci->eof && !fn->fci->error && fn->loaded < fn->bufsize)
+               goto again;
+       return pod->converted;
+}
+
+static void *oggdec_parse_config(int argc, char **argv)
+{
+       struct gengetopt_args_info *ret = para_calloc(sizeof(struct gengetopt_args_info));
+       if (!oggdec_cmdline_parser(argc, argv, ret))
+               return ret;
+       free(ret);
+       return NULL;
+}
+
+/** the init function of the ogg vorbis decoder */
+void oggdec_init(struct filter *f)
+{
+       f->open = ogg_open;
+       f->close = ogg_close;
+       f->convert = ogg_convert;
+       f->print_help = oggdec_cmdline_parser_print_help;
+       f->parse_config = oggdec_parse_config;
+}
diff --git a/oggdec_filter.ggo b/oggdec_filter.ggo
new file mode 100644 (file)
index 0000000..e9946ba
--- /dev/null
@@ -0,0 +1,2 @@
+option "bufsize" b "size of output buffer" int typestr="kilobyte" default="128" no
+option "initial_buffer" i "size of initial input buffer" int typestr="kilobyte" default="16" no
diff --git a/ortp.h b/ortp.h
new file mode 100644 (file)
index 0000000..0222290
--- /dev/null
+++ b/ortp.h
@@ -0,0 +1,30 @@
+/** \file ortp.h some macros used by ortp_send.c and ortp_recv.c*/
+enum ortp_audio_packet_type {ORTP_EOF, ORTP_BOF, ORTP_HEADER, ORTP_DATA};
+
+#define ORTP_AUDIO_HEADER_LEN 10
+
+#define WRITE_PACKET_TYPE(buf, x) (buf)[0] = (unsigned char)((x)&0xff)
+#define READ_PACKET_TYPE(buf) (unsigned)(buf)[0]
+
+#define WRITE_CHUNK_TIME(buf, x) (buf)[1] = (unsigned char)((x)&0xff);\
+       (buf)[2] = (unsigned char)(((x)>>8)&0xff);\
+       (buf)[3] = (unsigned char)(((x)>>16)&0xff);\
+       (buf)[4] = (unsigned char)(((x)>>24)&0xff);
+#define READ_CHUNK_TIME(buf) (unsigned char)(buf)[1] + \
+       ((unsigned char)(buf)[2] << 8) + \
+       ((unsigned char)(buf)[3] << 16) + \
+       ((unsigned char)(buf)[4] << 24)
+
+#define WRITE_CHUNK_TS(buf, x) (buf)[5] = (unsigned char)((x) & 0xff); \
+       (buf)[6] = (unsigned char)(((x >> 8) & 0xff));
+#define READ_CHUNK_TS(buf) (unsigned char)(buf)[5] + \
+       ((unsigned char)(buf)[6] << 8)
+
+#define WRITE_STREAM_TYPE(buf, x) (buf)[7] = (unsigned char)((x)&0xff)
+#define READ_STREAM_TYPE(buf) (unsigned)(buf)[7]
+
+#define WRITE_HEADER_LEN(buf, x) (buf)[8] = (unsigned char)((x) & 0xff); \
+       (buf)[9] = (unsigned char)(((x >> 8) & 0xff));
+#define READ_HEADER_LEN(buf) (unsigned char)(buf)[8] + \
+       ((unsigned char)(buf)[9] << 8)
+
diff --git a/ortp_recv.c b/ortp_recv.c
new file mode 100644 (file)
index 0000000..71c8b10
--- /dev/null
@@ -0,0 +1,305 @@
+/*
+ * 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 ortp_recv.c paraslash's ortp receiver */
+
+#include "para.h"
+#include <ortp/ortp.h>
+
+#include "ortp.h"
+#include "recv.h"
+#include "ortp_recv.cmdline.h"
+
+#include "error.h"
+#include "audiod.h"
+#include "string.h"
+
+#define CHUNK_SIZE 128 * 1024
+
+extern gint msg_to_buf(mblk_t *, char *, gint);
+
+/**
+ * data specific to the ortp receiver
+ *
+ * \sa receiver receiver_node
+ */
+struct private_ortp_recv_data {
+
+/**
+ *
+ *
+ * whether a header was received
+ *
+ * A flag indicating whether the ortp receiver already received a packet which
+ * contains the audio file header.
+ *
+ * This flag has no effect if the audio stream indicates that no extra headers
+ * will be sent (mp3).  Otherwise, all data packets are dropped until the
+ * header is received.
+ */
+int have_header;
+/** the ortp session this instance of the receiver belongs to */
+RtpSession *session;
+/** the time the first data or header packet was received */
+struct timeval start;
+/** ETA of the next chunk */
+struct timeval next_chunk;
+/** number of consecutive bad chunks */
+int c_bad;
+/** the current timestamp which is passed to the receiving function of libortp */
+guint32 timestamp;
+/** \a timestamp increases by this amount */
+guint32 chunk_ts;
+};
+
+#if 1
+static void recalc_timestamp(struct receiver_node *rn)
+{
+       struct timeval now, tmp, min_delay = {0, 5000};
+       struct private_ortp_recv_data *pord = rn->private_data;
+
+       gettimeofday(&now, NULL);
+       tv_add(&now, &min_delay, &pord->next_chunk);
+       if (pord->start.tv_sec) {
+               int new_ts;
+
+               tv_diff(&now, &pord->start, &tmp);
+               new_ts = rtp_session_time_to_ts(pord->session, tv2ms(&tmp));
+//             if (pord->c_bad > 50)
+                       pord->timestamp = new_ts;
+       } else
+               pord->timestamp = 0;
+       PARA_DEBUG_LOG("ts=%u, c_bad = %d\n", pord->timestamp, pord->c_bad);
+
+}
+#endif
+
+static int ortp_recv_pre_select(struct receiver_node *rn,
+               __unused fd_set *rfds, __unused fd_set *wfds,
+               struct timeval *timeout)
+{
+       struct private_ortp_recv_data *pord = rn->private_data;
+       struct timeval now, tmp;
+
+       gettimeofday(&now, NULL);
+       if (tv_diff(&now, &pord->next_chunk, &tmp) >= 0) {
+               tmp.tv_sec = 0;
+               tmp.tv_usec = 1000;
+       }
+       if (tv_diff(timeout, &tmp, NULL) > 0)
+               *timeout = tmp;
+       return -1; /* we did not modify the fd sets */
+}
+
+static void compute_next_chunk(struct timeval *now, unsigned chunk_time,
+               struct private_ortp_recv_data *pord)
+{
+       struct timeval chunk_tv = {0, chunk_time};
+//     if (!pord->start.tv_sec) {
+//             pord->start = *now;
+//             tv_add(&chunk_tv, now, &pord->next_chunk);
+//     } else
+       {
+               struct timeval tmp;
+               tv_add(&chunk_tv, &pord->next_chunk, &tmp);
+               pord->next_chunk = tmp;
+       }
+       pord->timestamp += pord->chunk_ts;
+       PARA_DEBUG_LOG("next chunk (ts = %d) due at %lu:%lu\n",
+               pord->timestamp, pord->next_chunk.tv_sec,
+               pord->next_chunk.tv_usec);
+}
+/** \cond */
+#define BUF_TO_VAL(buf) (((unsigned)(buf)[1] & 0xff) +  256 * ((unsigned)(buf)[0] & 0xff))
+/** \endcond */
+
+
+static int ortp_recv_post_select(struct receiver_node *rn, int select_ret,
+       __unused fd_set *rfds, __unused fd_set *wfds)
+{
+       struct private_ortp_recv_data *pord = rn->private_data;
+       mblk_t *mp;
+       int ret, packet_type, stream_type;
+       char tmpbuf[CHUNK_SIZE + 3];
+       struct timeval now;
+       unsigned chunk_time;
+
+       gettimeofday(&now, NULL);
+       PARA_DEBUG_LOG("rn: %p, pord: %p, session: %p\n", rn, pord, pord->session);
+       if (pord->start.tv_sec) {
+               struct timeval diff;
+               if (tv_diff(&now, &pord->next_chunk, &diff) < 0) {
+                       long unsigned diff_ms = tv2ms(&diff);
+                       if (diff_ms > 5) {
+                               PARA_DEBUG_LOG("too early: %lu ms left\n",
+                                       diff_ms);
+                               return 1;
+                       }
+               }
+       }
+       mp = rtp_session_recvm_with_ts(pord->session, pord->timestamp);
+       if (!mp) {
+               pord->c_bad++;
+               if ((pord->c_bad > 500 && pord->start.tv_sec) || pord->c_bad > 10000)
+                       return -E_TOO_MANY_BAD_CHUNKS;
+//             PARA_NOTICE_LOG("did not receive buffer, ts=%u, c_bad = %d, to = %lu\n",
+//                     pord->timestamp, pord->c_bad, tv2ms(&tmp));
+               if (pord->c_bad > 100)
+                       recalc_timestamp(rn);
+               return 1;
+       }
+       /* okay, we have a chunk of data */
+       PARA_DEBUG_LOG("have it ts = %d, chunk_ts = %d, loaded: %d, bad: %d\n",
+               pord->timestamp, pord->chunk_ts, rn->loaded, pord->c_bad);
+       if (!pord->start.tv_sec)
+               pord->start = now;
+       ret = msg_to_buf(mp, tmpbuf, CHUNK_SIZE);
+       if (ret < ORTP_AUDIO_HEADER_LEN) {
+               if (ret < 0)
+                       ret = -E_MSG_TO_BUF;
+               else
+                       ret = 0;
+               goto err_out;
+       }
+       packet_type = READ_PACKET_TYPE(tmpbuf);
+       stream_type = READ_STREAM_TYPE(tmpbuf);
+       chunk_time = READ_CHUNK_TIME(tmpbuf);
+       pord->chunk_ts = READ_CHUNK_TS(tmpbuf);
+       PARA_DEBUG_LOG("packet type: %d, stream type: %d, chunk time: %u, chunk_ts: %u\n", packet_type,
+               stream_type, chunk_time, pord->chunk_ts);
+       switch (packet_type) {
+       unsigned header_len, payload_len;
+       case ORTP_EOF:
+               ret = 0;
+               goto err_out;
+       case ORTP_BOF:
+               PARA_INFO_LOG("bof (%d)\n", ret);
+               pord->have_header = 1;
+               /* fall through */
+       case ORTP_DATA:
+               if (!pord->have_header && stream_type) /* can't use the data, wait for header */
+                       goto success;
+               if (ret + rn->loaded >= CHUNK_SIZE + ORTP_AUDIO_HEADER_LEN) {
+                       ret = -E_OVERRUN;
+                       goto err_out;
+               }
+               if (ret > ORTP_AUDIO_HEADER_LEN) {
+                       memcpy(rn->buf + rn->loaded, tmpbuf + ORTP_AUDIO_HEADER_LEN,
+                               ret - ORTP_AUDIO_HEADER_LEN);
+                       rn->loaded += ret - ORTP_AUDIO_HEADER_LEN;
+               }
+               goto success;
+       case ORTP_HEADER:
+               header_len = READ_HEADER_LEN(tmpbuf);
+               PARA_DEBUG_LOG("header packet (%d bytes), header len: %d\n", ret, header_len);
+               if (!pord->have_header) {
+                       pord->have_header = 1;
+                       memcpy(rn->buf, tmpbuf + ORTP_AUDIO_HEADER_LEN,
+                               ret - ORTP_AUDIO_HEADER_LEN);
+                       rn->loaded = ret - ORTP_AUDIO_HEADER_LEN;
+                       goto success;
+               }
+               if (header_len + ORTP_AUDIO_HEADER_LEN > ret) {
+                       ret = -E_INVALID_HEADER;
+                       goto err_out;
+               }
+               payload_len = ret - ORTP_AUDIO_HEADER_LEN - header_len;
+//             PARA_INFO_LOG("len: %d header_len: %d, payload_len: %d, loaded: %d\n", ret,
+//                     header_len, payload_len, rn->loaded);
+               if (rn->loaded + payload_len > CHUNK_SIZE) {
+                       ret = -E_OVERRUN;
+                       goto err_out;
+               }
+               if (payload_len)
+                       memcpy(rn->buf + rn->loaded, tmpbuf
+                               + (ret - payload_len), payload_len);
+               rn->loaded += payload_len;
+               goto success;
+       }
+success:
+       freemsg(mp);
+       pord->c_bad = 0;
+       compute_next_chunk(&now, chunk_time, pord);
+       return 1;
+err_out:
+       freemsg(mp);
+       return ret;
+}
+
+static void ortp_shutdown(void)
+{
+//     ortp_global_stats_display();
+       ortp_exit();
+}
+
+static void ortp_recv_close(struct receiver_node *rn)
+{
+       struct private_ortp_recv_data *pord = rn->private_data;
+
+       rtp_session_destroy(pord->session);
+       free(rn->private_data);
+       free(rn->buf);
+}
+
+static void *ortp_recv_parse_config(int argc, char **argv)
+{
+       int ret;
+
+       struct gengetopt_args_info *tmp = para_calloc(sizeof(struct gengetopt_args_info));
+
+       ret = ortp_recv_cmdline_parser(argc, argv, tmp)? -E_ORTP_SYNTAX : 1;
+       if (ret > 0)
+               return tmp;
+       free(tmp);
+       return NULL;
+}
+
+static int ortp_recv_open(struct receiver_node *rn)
+{
+       struct private_ortp_recv_data *pord;
+       struct gengetopt_args_info *conf = rn->conf;
+
+       rn->buf = para_calloc(CHUNK_SIZE);
+
+       rn->private_data = para_calloc(sizeof(struct private_ortp_recv_data));
+       pord = rn->private_data;
+       pord->session = rtp_session_new(RTP_SESSION_RECVONLY);
+       PARA_NOTICE_LOG("receiving from %s:%d\n", conf->host_arg, conf->port_arg);
+       rtp_session_set_local_addr(pord->session, conf->host_arg, conf->port_arg);
+       rtp_session_set_payload_type(pord->session, PAYLOAD_AUDIO_CONTINUOUS);
+       return 1;
+}
+
+/**
+ * the init function of the ortp receiver
+ *
+ * \param r pointer to the receiver struct to initialize
+ *
+ * Initialize all function pointers of \a r and call libortp's ortp_init().
+ */
+void ortp_recv_init(struct receiver *r)
+{
+       r->shutdown = ortp_shutdown;
+       r->open = ortp_recv_open;
+       r->close = ortp_recv_close;
+       r->pre_select = ortp_recv_pre_select;
+       r->post_select = ortp_recv_post_select;
+       r->parse_config = ortp_recv_parse_config;
+
+       ortp_init();
+       ortp_set_debug_file("oRTP", NULL);
+}
diff --git a/ortp_recv.ggo b/ortp_recv.ggo
new file mode 100644 (file)
index 0000000..6902fed
--- /dev/null
@@ -0,0 +1,3 @@
+section "ortp options"
+option "host" i "ip or host to receive rtp packets from" string default="224.0.1.38" no
+option "port" p "udp port." int typestr="portnumber" default="1500" no
diff --git a/ortp_send.c b/ortp_send.c
new file mode 100644 (file)
index 0000000..1521b63
--- /dev/null
@@ -0,0 +1,334 @@
+/*
+ * 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 ortp_send.c para_server's ortp sender */
+
+
+#include "server.cmdline.h"
+#include "server.h"
+#include "afs.h"
+#include "send.h"
+#include "list.h"
+#include <ortp/ortp.h>
+#include "ortp.h"
+#include "string.h"
+
+/** \cond convert in_addr to ascii */
+#define TARGET_ADDR(oc) inet_ntoa((oc)->addr)
+/** \endcond */
+
+extern struct gengetopt_args_info conf;
+
+/** describes one entry in the list of targets for the ortp sender */
+struct ortp_target {
+/** address info */
+       struct in_addr addr;
+/** whether the ortp sender is activated */
+       int status;
+/** the ortp timestamp increases by this amount */
+       int chunk_ts;
+/** the currently used timestamp for this target */
+       int last_ts;
+/** the position of this target in the list of targets */
+       struct list_head node;
+/** the UDP port */
+       int port;
+/** non-zero if at least one chunk has been sent to this target */
+       int streaming;
+/** the session pointer from libortp */
+       RtpSession *session;
+};
+
+static int numtargets;
+static struct list_head targets;
+static struct sender *self;
+
+static void ortp_delete_target(struct ortp_target *ot, const char *msg)
+{
+       PARA_NOTICE_LOG("deleting %s:%d (%s) from list\n", TARGET_ADDR(ot),
+               ot->port, msg);
+       if (ot->session) {
+               rtp_session_destroy(ot->session);
+               ot->session = NULL;
+       }
+       numtargets--;
+       list_del(&ot->node);
+       free(ot);
+}
+
+static void ortp_send_buf(char *buf, int len, long unsigned chunks_sent)
+{
+       struct ortp_target *ot, *tmp;
+       int ret;
+
+       list_for_each_entry_safe(ot, tmp, &targets, node) {
+               struct timeval now;
+               int ts;
+               gettimeofday(&now, NULL);
+               if (!ot->session)
+                       continue;
+               WRITE_CHUNK_TS(buf, ot->chunk_ts);
+               ts = ot->chunk_ts * chunks_sent;
+               ret = rtp_session_send_with_ts(ot->session, buf, len, ts);
+               ot->last_ts = ts;
+               if (ret < 0)
+                       ortp_delete_target(ot, "send error");
+               if (ret != len +12)
+                       PARA_NOTICE_LOG("short write %d\n", ret);
+       }
+}
+
+static void ortp_init_session(struct ortp_target *ot)
+{
+       RtpSession *s;
+       int ret;
+
+       PARA_NOTICE_LOG("sending to udp %s:%d\n", TARGET_ADDR(ot), ot->port);
+       ot->session = rtp_session_new(RTP_SESSION_SENDONLY);
+       if (!ot->session)
+               return;
+       s = ot->session;
+//     rtp_session_set_jitter_compensation(ot->session, 100);
+       /* always successful */
+       rtp_session_set_send_payload_type(s, PAYLOAD_AUDIO_CONTINUOUS);
+       ret = rtp_session_set_remote_addr(s, TARGET_ADDR(ot), ot->port);
+       if (ret < 0) {
+               rtp_session_destroy(ot->session);
+               ot->session = NULL;
+       }
+}
+
+/* called by afs */
+static void ortp_shutdown_targets(void)
+{
+       char buf[2];
+       struct ortp_target *ot, *tmp;
+
+       buf[0] = ORTP_EOF;
+       list_for_each_entry_safe(ot, tmp, &targets, node) {
+               if (!ot->session || !ot->streaming)
+                       continue;
+               PARA_INFO_LOG("sending eof to ortp target %s:%d, ts = %d\n", TARGET_ADDR(ot), ot->port,
+                       ot->last_ts);
+               rtp_session_send_with_ts(ot->session, buf, 1, ot->last_ts);
+               ot->streaming = 0;
+               ot->chunk_ts = 0;
+               rtp_session_reset(ot->session);
+       }
+}
+
+static int need_extra_header(struct audio_format *af, long unsigned chunks_sent)
+{
+       /* FIXME: No need to compute this on every run */
+       int mod = conf.ortp_header_interval_arg / (tv2ms(&af->chunk_tv) + 1);
+       if (mod && (chunks_sent % mod))
+               return 0;
+       return 1;
+}
+
+static void ortp_send(struct audio_format *af, long unsigned current_chunk,
+               long unsigned chunks_sent, const char *buf, size_t len)
+{
+       struct ortp_target *ot, *tmp;
+       size_t sendbuf_len;
+       size_t header_len = 0;
+       int packet_type = ORTP_DATA, stream_type = af && af->get_header_info; /* header stream? */
+       char *sendbuf, *header_buf = NULL;
+
+       if (self->status != SENDER_ON)
+               return;
+       list_for_each_entry_safe(ot, tmp, &targets, node) {
+               if (!ot->session) {
+                       ortp_init_session(ot);
+                       if (!ot->session)
+                               continue;
+               }
+               if (!ot->chunk_ts)
+                       ot->chunk_ts = rtp_session_time_to_ts(ot->session, tv2ms(&af->chunk_tv));
+//             PARA_DEBUG_LOG("len: %d, af: %p, ts: %d\n", len, af, ot->chunk_ts);
+               ot->streaming = 1;
+       }
+       if (list_empty(&targets))
+               return;
+       if (stream_type) {
+               header_buf = af->get_header_info(&header_len);
+               if (!need_extra_header(af, chunks_sent))
+                       header_len = 0;
+       }
+       sendbuf_len = ORTP_AUDIO_HEADER_LEN + header_len + len;
+       sendbuf = para_malloc(sendbuf_len);
+       if (!current_chunk)
+               packet_type = ORTP_BOF;
+       else if (header_len)
+               packet_type = ORTP_HEADER;
+       WRITE_PACKET_TYPE(sendbuf, packet_type);
+       WRITE_CHUNK_TIME(sendbuf, af->chunk_tv.tv_usec);
+       WRITE_STREAM_TYPE(sendbuf, stream_type);
+       WRITE_HEADER_LEN(sendbuf, header_len);
+       if (header_len)
+               memcpy(sendbuf + ORTP_AUDIO_HEADER_LEN, header_buf,
+                       header_len);
+       memcpy(sendbuf + ORTP_AUDIO_HEADER_LEN + header_len, buf, len);
+       ortp_send_buf(sendbuf, sendbuf_len, chunks_sent);
+       free(sendbuf);
+}
+
+static int ortp_com_on(struct sender_command_data *scd)
+{
+
+       self->status = SENDER_ON;
+       return 1;
+}
+
+static int ortp_com_off(struct sender_command_data *scd)
+{
+       ortp_shutdown_targets();
+       self->status = SENDER_OFF;
+       return 1;
+}
+
+static int ortp_com_delete(struct sender_command_data *scd)
+{
+       char *a = para_strdup(inet_ntoa(scd->addr));
+       struct ortp_target *ot, *tmp;
+       list_for_each_entry_safe(ot, tmp, &targets, node) {
+               if (scd->port != ot->port)
+                       continue;
+               if (strcmp(TARGET_ADDR(ot), a))
+                       continue;
+               ortp_delete_target(ot, "com_delete");
+       }
+       return 1;
+}
+
+static void ortp_add_target(int port, struct in_addr *addr)
+{
+       struct ortp_target *ot = para_calloc(sizeof(struct ortp_target));
+       ot->port = port;
+       ot->addr = *addr;
+       PARA_INFO_LOG("adding to target list (%s:%d)\n",
+               TARGET_ADDR(ot), ot->port);
+       list_add(&ot->node, &targets);
+}
+
+static int ortp_com_add(struct sender_command_data *scd)
+{
+       int port = (scd->port > 0)? scd->port : conf.ortp_default_port_arg;
+       ortp_add_target(port, &scd->addr);
+       return 1;
+}
+
+static char *ortp_info(void)
+{
+       struct ortp_target *ot;
+       char *ret, *tgts = NULL;
+
+       list_for_each_entry(ot, &targets, node) {
+               char *tmp = make_message("%s%s:%d ", tgts? tgts : "",
+                       TARGET_ADDR(ot), ot->port);
+               free(tgts);
+               tgts = tmp;
+       }
+       ret = make_message(
+               "ortp status: %s\n"
+               "ortp default port: udp %d\n"
+               "ortp targets: %s\n",
+               (self->status == SENDER_ON)? "on" : "off",
+               conf.ortp_default_port_arg,
+               tgts? tgts : "(none)"
+       );
+       free(tgts);
+       return ret;
+}
+
+static void ortp_init_target_list(void)
+{
+       int i;
+
+       INIT_LIST_HEAD(&targets);
+       for (i = 0; i < conf.ortp_target_given; i++) {
+               char *arg = para_strdup(conf.ortp_target_arg[i]);
+               char *p = strchr(arg, ':');
+               int port;
+               struct in_addr addr;
+
+               if (!p)
+                       goto err;
+               *p = '\0';
+               if (!inet_aton(arg, &addr))
+                       goto err;
+               port = atoi(++p);
+               if (port < 0 || port > 65535)
+                       port = conf.ortp_default_port_arg;
+               ortp_add_target(port, &addr);
+               goto success;
+err:
+               PARA_CRIT_LOG("syntax error for ortp_target option "
+                       "#%d, ignoring\n", i);
+success:
+               free(arg);
+               continue;
+       }
+}
+
+static void ortp_pre_select(struct audio_format *af, int *max_fileno,
+               fd_set *rfds, fd_set *wfds)
+{
+       return;
+}
+
+static char *ortp_help(void)
+{
+       return make_message(
+               "usage: {on|off}\n"
+               "usage: {add|delete} IP port\n"
+               "example: add 224.0.1.38 1500 (LAN-streaming)\n"
+       );
+}
+
+/**
+ * the init function of para_server's ortp sender
+ *
+ * \param s pointer to the http sender struct
+ *
+ * It initializes all function pointers of \a s and the list of ortp targets.
+ */
+void ortp_send_init(struct sender *s)
+{
+       ortp_init();
+       ortp_set_debug_file("oRTP", NULL);
+       INIT_LIST_HEAD(&targets);
+       s->info = ortp_info;
+       s->help = ortp_help;
+       s->send = ortp_send;
+       s->pre_select = ortp_pre_select;
+       s->post_select = NULL;
+       s->shutdown_clients = ortp_shutdown_targets;
+       s->client_cmds[SENDER_ON] = ortp_com_on;
+       s->client_cmds[SENDER_OFF] = ortp_com_off;
+       s->client_cmds[SENDER_DENY] = NULL;
+       s->client_cmds[SENDER_ALLOW] = NULL;
+       s->client_cmds[SENDER_ADD] = ortp_com_add;
+       s->client_cmds[SENDER_DELETE] = ortp_com_delete;
+       self = s;
+       s->status = SENDER_OFF;
+       ortp_init_target_list();
+       if (!conf.ortp_no_autostart_given)
+               s->status = SENDER_ON;
+       PARA_DEBUG_LOG("%s", "ortp sender init complete\n");
+}
diff --git a/para.h b/para.h
new file mode 100644 (file)
index 0000000..049262d
--- /dev/null
+++ b/para.h
@@ -0,0 +1,227 @@
+/*
+ * 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 para.h global paraslash definitions */
+
+#include "config.h"
+
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <sys/signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h> /* time(), localtime() */
+#include <unistd.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stropts.h>
+#include <poll.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/ipc.h>
+#include <sys/sem.h>
+#include <ctype.h>
+#include <sys/un.h> /* needed by create_pf_socket */
+#include "gcc-compat.h"
+
+/* some internal constants */
+#define STRINGSIZE 4096
+#define MAXLINE 255
+
+
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+#define MAX(a,b) ((a) > (b) ? (a) : (b))
+#define ABS(a) ((a) > 0 ? (a) : -(a))
+
+/* Loglevels */
+#define DEBUG 1
+#define INFO  2
+#define NOTICE 3
+#define WARNING 4
+#define ERROR 5
+#define CRIT 6
+#define EMERG 7
+
+
+#define COMPILE_TIME_LOGLEVEL 0
+#if DEBUG > COMPILE_TIME_LOGLEVEL
+#define PARA_DEBUG_LOG(f,...) para_log(DEBUG, "%s: " f, __FUNCTION__, __VA_ARGS__)
+#else
+#define PARA_DEBUG_LOG(...)
+#endif
+
+#if INFO > COMPILE_TIME_LOGLEVEL
+#define PARA_INFO_LOG(f,...) para_log(INFO, "%s: " f, __FUNCTION__, __VA_ARGS__)
+#else
+#define PARA_INFO_LOG(...)
+#endif
+
+#if NOTICE > COMPILE_TIME_LOGLEVEL
+#define PARA_NOTICE_LOG(f,...) para_log(NOTICE, "%s: " f, __FUNCTION__, __VA_ARGS__)
+#else
+#define PARA_NOTICE_LOG(...)
+#endif
+
+#if WARNING > COMPILE_TIME_LOGLEVEL
+#define PARA_WARNING_LOG(f,...) para_log(WARNING, "%s: " f, __FUNCTION__, __VA_ARGS__)
+#else
+#define PARA_WARNING_LOG(...)
+#endif
+
+#if ERROR > COMPILE_TIME_LOGLEVEL
+#define PARA_ERROR_LOG(f,...) para_log(ERROR, "%s: " f, __FUNCTION__, __VA_ARGS__)
+#else
+#define PARA_ERROR_LOG(...)
+#endif
+
+#if CRIT > COMPILE_TIME_LOGLEVEL
+#define PARA_CRIT_LOG(f,...) para_log(CRIT, "%s: " f, __FUNCTION__, __VA_ARGS__)
+#else
+#define PARA_CRIT_LOG(...)
+#endif
+
+#if EMERG > COMPILE_TIME_LOGLEVEL
+#define PARA_EMERG_LOG(f,...) para_log(EMERG, "%s: " f, __FUNCTION__, __VA_ARGS__)
+#else
+#define PARA_EMERG_LOG(...)
+#endif
+
+#define COPYRIGHT "Copyright (c) 1997-2006 by Andre Noll"
+
+#define LICENSE "This is free software with ABSOLUTELY NO WARRANTY. " \
+       "See COPYING for details."
+
+#define AWAITING_DATA_MSG "\nAwaiting Data."
+#define PROCEED_MSG "\nProceed.\n"
+#define PROCEED_MSG_LEN strlen(PROCEED_MSG)
+#define EOC_MSG "\nEnd of Command."
+#define CHALLENGE_RESPONSE_MSG "challenge_response:"
+
+/* gui_common */
+int para_open_audiod_pipe(char *);
+int read_audiod_pipe(int, void (*)(char *));
+
+/* exec */
+int file_exists(const char *);
+int para_exec(pid_t *, const char *, char *const [], int *);
+int para_exec_cmdline_pid(pid_t *, char *, int *);
+
+/* signal */
+int para_signal_init(void);
+int para_install_sighandler(int);
+void para_reap_children(void);
+pid_t para_reap_child(void);
+int para_next_signal(void);
+
+/* time */
+int tv_diff(const struct timeval *b, const struct timeval *a, struct timeval *diff);
+long unsigned tv2ms(const struct timeval*);
+void d2tv(double, struct timeval*);
+void tv_add(const struct timeval*, const struct timeval *, struct timeval *);
+void tv_scale(const unsigned long, const struct timeval *, struct timeval *);
+void tv_divide(const unsigned long div, const struct timeval *tv,
+       struct timeval *result);
+int tv_convex_combination(const long a, const struct timeval *tv1,
+               const long b, const struct timeval *tv2,
+               struct timeval *result);
+void ms2tv(const long unsigned n, struct timeval *tv);
+
+/* stat */
+enum {
+       SI_STATUS_BAR,          SI_STATUS,              SI_NUM_PLAYED,
+       SI_MTIME,               SI_LENGTH_MIN,          SI_LENGTH_SEC,
+       SI_FILE_SIZE,           SI_STATUS_FLAGS,        SI_FORMAT,
+       SI_SCORE,               SI_AUDIO_INFO1,         SI_AUDIO_INFO2,
+       SI_AUDIO_INFO3,         SI_DBINFO1,             SI_DBINFO2,
+       SI_DBINFO3,             SI_DECODER_FLAGS,       SI_AUDIOD_STATUS,
+       SI_PLAY_TIME,           SI_UPTIME,              SI_OFFSET,
+       SI_LENGTH,              SI_STREAM_START,        SI_CURRENT_TIME,
+       SI_AUDIOD_UPTIME,       SI_DBTOOL,
+};
+#define NUM_STAT_ITEMS (SI_DBTOOL + 1)
+int stat_line_valid(const char *);
+void stat_client_write(char *msg);
+int stat_client_add(int);
+void dump_empty_status(void);
+unsigned for_each_line(char *, int, void (*)(char *), int);
+
+struct stat_item_data {
+       char *prefix, *postfix;
+       unsigned x, y, len;
+       int fg, bg, align;
+};
+
+/* gui_theme */
+struct gui_theme {
+       char *name;
+       char *author;
+       int sb_fg, sb_bg;
+       int cmd_fg, cmd_bg;
+       int output_fg, output_bg;
+       int msg_fg, msg_bg;
+       int err_msg_fg, err_msg_bg;
+       int welcome_fg, welcome_bg;
+       int sep_fg, sep_bg;
+       char *sep_str;
+       int default_fg, default_bg;
+
+       int top_lines_default, top_lines_min;
+       int lines_min, cols_min;
+       struct stat_item_data data[NUM_STAT_ITEMS];
+};
+
+void init_theme(int i, struct gui_theme *);
+void next_theme(struct gui_theme *);
+void prev_theme(struct gui_theme *);
+#define LEFT 1
+#define RIGHT 2
+#define CENTER 3
+
+
+__printf_2_3 void para_log(int, char*, ...);
+
+/* taken from printf man page */
+#define PARA_VSPRINTF(fmt, p) \
+{ \
+       int n, size = 100; \
+       p = para_malloc(size); \
+       while (1) { \
+               va_list ap; \
+               /* Try to print in the allocated space. */ \
+               va_start(ap, fmt); \
+               n = vsnprintf(p, size, fmt, ap); \
+               va_end(ap); \
+               /* If that worked, return the string. */ \
+               if (n > -1 && n < size) \
+                       break; \
+               /* Else try again with more space. */ \
+               if (n > -1) /* glibc 2.1 */ \
+                       size = n + 1; /* precisely what is needed */ \
+               else /* glibc 2.0 */ \
+                       size *= 2; /* twice the old size */ \
+               p = para_realloc(p, size); \
+       } \
+}
+
+
+
diff --git a/pics/paraslash/default.jpg b/pics/paraslash/default.jpg
new file mode 100644 (file)
index 0000000..2de4076
Binary files /dev/null and b/pics/paraslash/default.jpg differ
diff --git a/pics/screenshots/gui-2004-07-11.png b/pics/screenshots/gui-2004-07-11.png
new file mode 100644 (file)
index 0000000..51b1c3f
Binary files /dev/null and b/pics/screenshots/gui-2004-07-11.png differ
diff --git a/pics/screenshots/gui-2004-09-02.png b/pics/screenshots/gui-2004-09-02.png
new file mode 100644 (file)
index 0000000..ad6c9f5
Binary files /dev/null and b/pics/screenshots/gui-2004-09-02.png differ
diff --git a/pics/screenshots/gui-2005-11-12.png b/pics/screenshots/gui-2005-11-12.png
new file mode 100644 (file)
index 0000000..b3decd4
Binary files /dev/null and b/pics/screenshots/gui-2005-11-12.png differ
diff --git a/pics/screenshots/gui-old.png b/pics/screenshots/gui-old.png
new file mode 100644 (file)
index 0000000..74a81d5
Binary files /dev/null and b/pics/screenshots/gui-old.png differ
diff --git a/pics/screenshots/loglevel1-2004-07-28.txt b/pics/screenshots/loglevel1-2004-07-28.txt
new file mode 100644 (file)
index 0000000..2995f0b
--- /dev/null
@@ -0,0 +1,43 @@
+Jul 28 08:05:15 1: (29586) afs_send_chunk: Song finished (flags:8)
+Jul 28 08:05:15 1: (29586) get_song: Getting next song
+Jul 28 08:05:16 1: (29586) get_song: Opening /home/mp3/checked/cd_37/Rodgau_Monotones__Is_Mir_Egal.mp3
+Jul 28 08:05:16 1: (29586) update_mmd: initialising mmd struct 
+Jul 28 08:05:16 4: (29586) update_mmd: Next song: /home/mp3/checked/cd_37/Rodgau_Monotones__Is_Mir_Egal.mp3
+Jul 28 08:05:16 3: (29586) update_mmd: size: 4664869, mtime: Tue Jul 13 18:50:40 2004
+Jul 28 08:05:16 3: (29586) update_mmd: current stream: wake, num_played: 35
+Jul 28 08:05:16 3: (29586) update_mmd: length: 3:53, 160 KBit/s, 44100 KHz (joint stereo)
+Jul 28 08:05:16 1: (29586) update_mmd: mmd.length=233, mmd.size=4664869
+Jul 28 08:05:16 1: (29586) update_dbinfo: getting dbtool info /home/mp3/checked/cd_37/Rodgau_Monotones__Is_Mir_Egal.mp3
+Jul 28 08:05:16 3: (29586) update_dbinfo: dir: /home/mp3/checked/cd_37
+Jul 28 08:05:16 3: (29586) update_dbinfo: last played: 4 day(s) ago. Numplayed: 5, Pic_Id: 314
+Jul 28 08:05:16 3: (29586) update_dbinfo: attributes: fast fun deutsch rock
+Jul 28 08:05:16 1: (29586) update_dbinfo: Read 116 byte of info and put 140 byte into mmd->dbinfo
+Jul 28 08:05:16 1: (29586) update_mmd: calling dbtool to update song
+Jul 28 08:05:16 1: (29586) main: Caught signal 17
+Jul 28 08:05:16 1: (29586) handle_sigchld: child 3956 exited. Exit status: 0
+Jul 28 08:05:16 1: (29586) handle_sigchld: child 3959 exited. Exit status: 0
+Jul 28 08:05:16 1: (29586) main: Caught signal 17
+Jul 28 08:05:16 1: (29586) main: Caught signal 17
+Jul 28 08:05:16 1: (29586) handle_sigchld: child 3961 exited. Exit status: 0
+Jul 28 08:05:16 4: (29586) main: Got connection from 192.168.0.7
+Jul 28 08:05:16 1: (3963) handle_connect: Received auth request for user maan
+Jul 28 08:05:16 1: (3963) open_user_list: Opening config file /home/maan/.paraslash/server.users
+Jul 28 08:05:16 1: (3963) get_user: found entry for maan
+Jul 28 08:05:16 1: (3963) get_user: found 4 perm entries
+Jul 28 08:05:16 1: (3963) get_user: tmp[3]=AFS_WRITE
+Jul 28 08:05:16 1: (3963) get_user: tmp[2]=AFS_READ
+Jul 28 08:05:16 1: (3963) get_user: tmp[1]=DB_WRITE
+Jul 28 08:05:16 1: (3963) get_user: tmp[0]=DB_READ
+Jul 28 08:05:16 1: (3964) encrypt: dup2ing stdin
+Jul 28 08:05:16 1: (3964) encrypt: dup2ing stdout
+Jul 28 08:05:16 1: (3963) encrypt: Writing challenge to pipe (11 bytes)
+Jul 28 08:05:16 1: (3963) encrypt: Reading line from pipe 
+Jul 28 08:05:16 1: (3963) encrypt: Read 64 byte from pipe
+Jul 28 08:05:16 1: (3963) handle_connect: Sending 64 byte challenge for maan
+Jul 28 08:05:16 3: (3963) handle_connect: Good auth for maan (1894092672)
+Jul 28 08:05:16 1: (3963) parse_cmd: Found command pic. Perms: 1
+Jul 28 08:05:16 1: (3963) check_perms: check_perms
+Jul 28 08:05:16 4: (3963) handle_connect: executing command handler for command "pic"
+Jul 28 08:05:16 1: (29586) main: Caught signal 17
+Jul 28 08:05:16 1: (29586) handle_sigchld: child 3963 exited. Exit status: 0
+
diff --git a/pics/screenshots/loglevel1-2005-03-23.txt b/pics/screenshots/loglevel1-2005-03-23.txt
new file mode 100644 (file)
index 0000000..c1438f9
--- /dev/null
@@ -0,0 +1,65 @@
+Mar 23 20:47:26 3: (19185) do_inits: welcome to para_server 0.1.6-dev (Wed Mar 23 20:39:23 MET 2005)
+Mar 23 20:47:26 3: (19185)  
+Mar 23 20:47:26 1: (19185) do_inits: using loglevel 1
+Mar 23 20:47:26 4: (19185) init_dbtool: initializing database tool
+Mar 23 20:47:26 1: (19185) init_mysql_server: connecting: maan@localhost:3306
+Mar 23 20:47:26 3: (19185) init_mysql_server: successfully connected to mysql server
+Mar 23 20:47:26 3: (19185) init_dbtool: initialized mysql
+Mar 23 20:47:26 4: (19185) do_inits: initializing audio file sender
+Mar 23 20:47:26 3: (19185) afs_init: checking audio formats
+Mar 23 20:47:26 1: (19185) afs_init: supported audio format: mp3
+Mar 23 20:47:26 1: (19185) setup_stream_command: 1 stream write command given, checking for mp3
+Mar 23 20:47:26 3: (19185) setup_stream_command: using "poc-fec -q -" for files of type mp3 (default)
+Mar 23 20:47:26 1: (19185) afs_init: supported audio format: ogg
+Mar 23 20:47:26 1: (19185) setup_stream_command: 1 stream write command given, checking for ogg
+Mar 23 20:47:26 3: (19185) setup_stream_command: using "/home/maan/para/para_ovsend -r /home/maan/.paraslash/ogg_fifo.p133" for files of type ogg
+Mar 23 20:47:26 1: (19185) afs_init: supported audio formats: mp3 ogg 
+Mar 23 20:47:26 4: (19185) setup_signal_handling: setting up signal handlers
+Mar 23 20:47:26 1: (19185) para_install_sighandler: catching signal 2
+Mar 23 20:47:26 1: (19185) para_install_sighandler: catching signal 15
+Mar 23 20:47:26 1: (19185) para_install_sighandler: catching signal 1
+Mar 23 20:47:26 1: (19185) para_install_sighandler: catching signal 17
+Mar 23 20:47:26 4: (19185) do_inits: initializing networking
+Mar 23 20:47:26 3: (19185) init_network: init network socket
+Mar 23 20:47:26 4: (19185) do_inits: init complete
+Mar 23 20:47:26 1: (19185) afs_mainloop: not playing
+Mar 23 20:47:29 4: (19185) main: got connection from 192.168.0.8, forking
+Mar 23 20:47:29 1: (19185) afs_mainloop: not playing
+Mar 23 20:47:29 1: (19187) handle_connect: Received auth request for user maan
+Mar 23 20:47:29 1: (19187) open_user_list: Opening user list /home/maan/.paraslash/server.users
+Mar 23 20:47:29 1: (19187) get_user: found entry for maan
+Mar 23 20:47:29 1: (19187) get_user: found 4 perm entries
+Mar 23 20:47:29 3: (19187) encrypt: writing challenge to pipe (11 bytes)
+Mar 23 20:47:29 1: (19187) encrypt: reading response
+Mar 23 20:47:29 1: (19187) encrypt: read 64 byte from pipe
+Mar 23 20:47:29 1: (19187) handle_connect: sending 64 byte challenge
+Mar 23 20:47:29 3: (19187) handle_connect: good auth for maan (3859497713)
+Mar 23 20:47:29 1: (19187) parse_cmd: found command play.
+Mar 23 20:47:29 1: (19187) check_perms: checking permissions
+Mar 23 20:47:29 4: (19187) handle_connect: executing command handler for command "play"
+Mar 23 20:47:29 3: (19187) handle_connect: command handler returned success
+Mar 23 20:47:29 1: (19185) afs_mainloop: no audio file
+Mar 23 20:47:29 1: (19185) real_query: select def from streams where name = 'current_stream'
+Mar 23 20:47:29 1: (19185) real_query: select def from streams where name='gulp'
+Mar 23 20:47:29 1: (19185) real_query: select concat(dir.dir, '/', dir.name) from data,dir where dir.name = data.name  and ( (dir.name like '%G.U.L.P%'))  order by -((FLOOR((UNIX_TIMESTAMP(now())-UNIX_TIMESTAMP(data.Lastplayed))/60) / 1440 - sqrt(data.Numplayed)))
+Mar 23 20:47:29 3: (19185) get_song: trying /home/mp3/checked/cd_20/The_G.U.L.P.__U-Bahn.mp3
+Mar 23 20:47:29 1: (19185) guess_audio_format: might be mp3
+Mar 23 20:47:29 3: (19185) get_file_info: valid mp3 file
+Mar 23 20:47:29 4: (19185) update_mmd: next audio file: /home/mp3/checked/cd_20/The_G.U.L.P.__U-Bahn.mp3
+Mar 23 20:47:29 1: (19185) real_query: select def from streams where name = 'current_stream'
+Mar 23 20:47:29 1: (19185) real_query: select concat('lastplayed: ', (to_days(now()) - to_days(lastplayed)),' day(s). numplayed: ', numplayed, ', pic: ', pic_id) from data where name = 'The_G.U.L.P.__U-Bahn.mp3'
+Mar 23 20:47:29 1: (19185) real_query: desc data
+Mar 23 20:47:29 1: (19185) real_query: select * from data where name='The_G.U.L.P.__U-Bahn.mp3'
+Mar 23 20:47:29 1: (19185) real_query: select dir from dir where name = 'The_G.U.L.P.__U-Bahn.mp3'
+Mar 23 20:47:29 1: (19185) real_query: select def from streams where name='gulp'
+Mar 23 20:47:29 1: (19185) real_query: select (FLOOR((UNIX_TIMESTAMP(now())-UNIX_TIMESTAMP(data.Lastplayed))/60) / 1440 - sqrt(data.Numplayed)) from data  where name = 'The_G.U.L.P.__U-Bahn.mp3'  
+Mar 23 20:47:29 1: (19185) update_mmd: updating shared memory area
+Mar 23 20:47:29 1: (19185) real_query: update data set lastplayed = now() where name='The_G.U.L.P.__U-Bahn.mp3'
+Mar 23 20:47:29 1: (19185) real_query: select numplayed from data where name = 'The_G.U.L.P.__U-Bahn.mp3'
+Mar 23 20:47:29 1: (19185) real_query: update data set numplayed=38 where name='The_G.U.L.P.__U-Bahn.mp3'
+Mar 23 20:47:29 1: (19185) get_song: success
+Mar 23 20:47:29 1: (19185) afs_mainloop: open stream
+Mar 23 20:47:29 3: (19185) afs_open_stream_writer: opening mp3 stream_writer (poc-fec -q -)
+Mar 23 20:47:29 1: (19185) afs_open_stream_writer: mp3 stream writer pid: 19189, fd: 10
+Mar 23 20:47:29 1: (19185) para_next_signal: next signal: 17
+Mar 23 20:47:29 1: (19185) para_reap_child: child 19187 exited. Exit status: 0
diff --git a/pics/screenshots/para_audiod-startup.txt b/pics/screenshots/para_audiod-startup.txt
new file mode 100644 (file)
index 0000000..1296e8b
--- /dev/null
@@ -0,0 +1,33 @@
+Jan 22 03:17:31 meins 2 log_welcome: welcome to para_audiod cvs (Sun Jan 22 02:51:13 MET 2006)
+Jan 22 03:17:31 meins 2 init_stream_io: initializing http receiver
+Jan 22 03:17:31 meins 2 init_stream_io: initializing ortp receiver
+Jan 22 03:17:31 meins 2 init_stream_io: initializing wav filter
+Jan 22 03:17:31 meins 2 init_stream_io: initializing compress filter
+Jan 22 03:17:31 meins 2 init_stream_io: initializing mp3dec filter
+Jan 22 03:17:31 meins 2 init_stream_io: initializing oggdec filter
+Jan 22 03:17:31 meins 2 add_filter: mp3 filter 1: mp3dec
+Jan 22 03:17:31 meins 2 add_filter: mp3 filter 2: compress
+Jan 22 03:17:31 meins 2 add_filter: mp3 filter 3: wav
+Jan 22 03:17:31 meins 2 add_filter: ogg filter 1: oggdec
+Jan 22 03:17:31 meins 2 add_filter: ogg filter 2: compress
+Jan 22 03:17:31 meins 2 add_filter: ogg filter 3: wav
+Jan 22 03:17:31 meins 2 clear_slot: clearing slot 0
+Jan 22 03:17:31 meins 2 clear_slot: clearing slot 1
+Jan 22 03:17:31 meins 2 clear_slot: clearing slot 2
+Jan 22 03:17:31 meins 2 clear_slot: clearing slot 3
+Jan 22 03:17:31 meins 2 clear_slot: clearing slot 4
+Jan 22 03:17:31 meins 2 init_grabbing: grab init
+Jan 22 03:17:31 meins 2 setup_signal_handling: signal pipe: fd 4
+Jan 22 03:17:31 meins 2 daemon_init: daemonizing
+Jan 22 03:17:31 meins 3 open_stat_pipe: stat pipe opened, fd 6
+Jan 22 03:17:31 meins 3 audiod_get_socket: connecting to local socket /var/paraslash/audiod_socket.meins
+Jan 22 03:17:32 meins 2 handle_connect: pid: 8807, uid: 409, gid: 100, ret: 254, buf: stat
+Jan 22 03:17:32 meins 2 handle_connect: argv[0]: stat
+Jan 22 03:17:32 meins 2 stat_client_add: adding client on fd 8
+Jan 22 03:17:32 meins 2 dump_stat_client_list: stat client on fd 8
+Jan 22 03:17:32 meins 2 handle_connect: pid: 8806, uid: 409, gid: 100, ret: 254, buf: stat
+Jan 22 03:17:32 meins 2 handle_connect: argv[0]: stat
+Jan 22 03:17:32 meins 2 stat_client_add: adding client on fd 9
+Jan 22 03:17:32 meins 2 dump_stat_client_list: stat client on fd 9
+Jan 22 03:17:32 meins 2 dump_stat_client_list: stat client on fd 8
+Jan 22 03:17:46 meins 2 compute_time_diff: time diff (cur/avg): 0:1/0:0
diff --git a/pics/screenshots/para_krell-2005-02.png b/pics/screenshots/para_krell-2005-02.png
new file mode 100644 (file)
index 0000000..1aa25d8
Binary files /dev/null and b/pics/screenshots/para_krell-2005-02.png differ
diff --git a/pics/screenshots/para_server-startup.txt b/pics/screenshots/para_server-startup.txt
new file mode 100644 (file)
index 0000000..f0a6292
--- /dev/null
@@ -0,0 +1,56 @@
+Jan 22 03:08:46 2: (27321) log_welcome: welcome to para_server cvs (Sun Jan 22 02:51:13 MET 2006)
+Jan 22 03:08:46 1: (27321) log_welcome: using loglevel 1
+Jan 22 03:08:46 2: (27321) daemon_init: daemonizing
+Jan 22 03:08:46 3: (27322) init_dbtool: initializing mysql database tool
+Jan 22 03:08:46 1: (27322) init_mysql_server: connecting: maan@localhost:3306
+Jan 22 03:08:46 2: (27322) init_mysql_server: successfully connected to mysql server
+Jan 22 03:08:46 2: (27322) init_dbtool: initialized mysql
+Jan 22 03:08:46 3: (27322) do_inits: initializing audio file sender
+Jan 22 03:08:46 1: (27322) afs_init: supported audio formats: mp3 ogg
+Jan 22 03:08:46 3: (27322) afs_init: initializing mp3 handler
+Jan 22 03:08:46 3: (27322) afs_init: initializing ogg handler
+Jan 22 03:08:46 2: (27322) afs_init: announce timeval: 0:300000
+Jan 22 03:08:46 3: (27322) afs_init: initializing http sender
+Jan 22 03:08:46 2: (27322) add_perm_list_entry: adding 192.168.0.1/32 to access list
+Jan 22 03:08:46 2: (27322) add_perm_list_entry: adding 192.168.0.4/32 to access list
+Jan 22 03:08:46 2: (27322) add_perm_list_entry: adding 192.168.0.8/32 to access list
+Jan 22 03:08:46 2: (27322) init_tcp_socket: listening on port 8000, fd 5
+Jan 22 03:08:46 1: (27322) para_http_init: http sender init complete
+Jan 22 03:08:46 3: (27322) afs_init: initializing ortp sender
+Jan 22 03:08:46 2: (27322) ortp_add_target: adding to target list (224.0.1.38:1500)
+Jan 22 03:08:46 1: (27322) para_ortp_init: ortp sender init complete
+Jan 22 03:08:46 3: (27322) setup_signal_handling: setting up signal handlers
+Jan 22 03:08:46 1: (27322) para_install_sighandler: catching signal 2
+Jan 22 03:08:46 1: (27322) para_install_sighandler: catching signal 15
+Jan 22 03:08:46 1: (27322) para_install_sighandler: catching signal 1
+Jan 22 03:08:46 1: (27322) para_install_sighandler: catching signal 17
+Jan 22 03:08:46 3: (27322) do_inits: initializing tcp command socket
+Jan 22 03:08:46 2: (27322) init_tcp_socket: listening on port 2990, fd 8
+Jan 22 03:08:46 3: (27322) do_inits: init complete
+Jan 22 03:08:46 1: (27322) status_refresh: 0 events, forcing status update, af = -1
+Jan 22 03:08:47 2: (27322) main: got connection from 192.168.0.8, forking
+Jan 22 03:08:47 1: (27328) close_listed_fds: closing fd 5
+Jan 22 03:08:47 1: (27328) handle_connect: received rc4 request for user maan
+Jan 22 03:08:47 1: (27328) open_user_list: opening user list /home/maan/.paraslash/server.users
+Jan 22 03:08:47 1: (27328) get_user: found entry for maan
+Jan 22 03:08:47 1: (27328) get_user: found 4 perm entries
+Jan 22 03:08:47 1: (27328) handle_connect: sending 64 byte challenge
+Jan 22 03:08:47 2: (27328) handle_connect: good auth for maan (1359837743)
+Jan 22 03:08:47 1: (27328) init_rc4_keys: rc4 keys initialized (198:20)
+Jan 22 03:08:47 2: (27328) handle_connect: rc4 encrytion activated
+Jan 22 03:08:47 1: (27328) parse_cmd: found command stat
+Jan 22 03:08:47 1: (27328) check_perms: checking permissions
+Jan 22 03:08:47 3: (27328) handle_connect: calling com_stat() for maan@192.168.0.8
+Jan 22 03:08:47 2: (27322) main: got connection from 192.168.0.4, forking
+Jan 22 03:08:47 1: (27329) close_listed_fds: closing fd 5
+Jan 22 03:08:47 1: (27329) handle_connect: received rc4 request for user maan
+Jan 22 03:08:47 1: (27329) open_user_list: opening user list /home/maan/.paraslash/server.users
+Jan 22 03:08:47 1: (27329) get_user: found entry for maan
+Jan 22 03:08:47 1: (27329) get_user: found 4 perm entries
+Jan 22 03:08:47 1: (27329) handle_connect: sending 64 byte challenge
+Jan 22 03:08:47 2: (27329) handle_connect: good auth for maan (1661875263)
+Jan 22 03:08:47 1: (27329) init_rc4_keys: rc4 keys initialized (246:109)
+Jan 22 03:08:47 2: (27329) handle_connect: rc4 encrytion activated
+Jan 22 03:08:47 1: (27329) parse_cmd: found command stat
+Jan 22 03:08:47 1: (27329) check_perms: checking permissions
+Jan 22 03:08:47 3: (27329) handle_connect: calling com_stat() for maan@192.168.0.4
diff --git a/pics/screenshots/para_slider-2004-12.png b/pics/screenshots/para_slider-2004-12.png
new file mode 100644 (file)
index 0000000..f997fba
Binary files /dev/null and b/pics/screenshots/para_slider-2004-12.png differ
diff --git a/pics/screenshots/sdl_gui.jpg b/pics/screenshots/sdl_gui.jpg
new file mode 100644 (file)
index 0000000..f6e3d48
Binary files /dev/null and b/pics/screenshots/sdl_gui.jpg differ
diff --git a/pics/web/paraslash.ico b/pics/web/paraslash.ico
new file mode 100644 (file)
index 0000000..a6b29dd
Binary files /dev/null and b/pics/web/paraslash.ico differ
diff --git a/pics/web/paraslash.png b/pics/web/paraslash.png
new file mode 100644 (file)
index 0000000..d8082c6
Binary files /dev/null and b/pics/web/paraslash.png differ
diff --git a/play.c b/play.c
new file mode 100644 (file)
index 0000000..99c2316
--- /dev/null
+++ b/play.c
@@ -0,0 +1,342 @@
+/*
+ * 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.
+ */
+
+/*
+ * Based in parts on aplay.c from the alsa-utils-1.0.8 package,
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>, which is
+ * based on the vplay program by Michael Beck.
+ */
+
+#define BUFFER_SIZE 1000 * 1000
+#define WAV_HEADER_LEN 44
+#include <sys/time.h> /* gettimeofday */
+#include "para.h"
+#include "play.cmdline.h"
+#include <alsa/asoundlib.h>
+
+enum { E_BROKEN_CONF,          /* Broken configuration for this PCM */
+       E_ACCESS_TYPE,          /* Access type not available */
+       E_SAMPLE_FORMAT,        /* Sample format not available */
+       E_CHANNEL_COUNT,        /* Channels count not available */
+       E_HW_PARAMS,            /* Unable to install hw params */
+       E_SW_PARAMS,            /* Unable to install sw params */
+       E_BAD_PERIOD,           /* Can't use period equal to buffer size */
+       E_GET_XFER,             /* Unable to obtain xfer align */
+       E_SET_XFER,             /* snd_pcm_sw_params_set_xfer_align failed */
+       E_MEM,                  /* not enough memory */
+       E_READ,                 /* read error */
+       E_WRITE,                /* write error */
+       E_PIPE,                 /* write to pipe with other side closed */
+       E_PCM_OPEN,             /* unable to open pcm */
+       E_SND_PCM_INFO,         /* pcm info error */
+       E_GET_BUFFER_TIME,      /* snd_pcm_hw_params_get_buffer_time_max failed */
+       E_SET_BUFFER_TIME,      /* snd_pcm_hw_params_set_buffer_time_near failed */
+       E_SET_RATE,             /* snd_pcm_hw_params_set_rate_near failed */
+       E_START_THRESHOLD,      /* snd_pcm_sw_params_set_start_threshold failed */
+       E_STOP_THRESHOLD,       /* snd_pcm_sw_params_set_stop_threshold failed */
+       E_LOG,                  /* snd_output_stdio_attach failed */
+       E_SYNTAX                /* could not parse start_time option */
+};
+
+#define FORMAT SND_PCM_FORMAT_S16_LE
+
+#define EXIT(EXP) \
+do { if (EXP) \
+        fprintf (stderr, "error: " #EXP "\n"); exit(EXP);} \
+while (0)
+
+static snd_pcm_t *handle;
+static unsigned char *audiobuf;
+static snd_pcm_uframes_t chunk_size;
+static size_t bytes_per_frame;
+static struct timeval *start_time;
+static struct gengetopt_args_info conf;
+
+/*
+ * read_wav_header - read WAV_HEADER_LEN bytes from stdin to audio buffer
+ *
+ * Exit on errors and on eof before WAV_HEADER_LEN could be read.
+ */
+static void read_wav_header(void)
+{
+       ssize_t ret, count = 0;
+
+       while (count < WAV_HEADER_LEN) {
+               ret = read(STDIN_FILENO, audiobuf + count, WAV_HEADER_LEN - count);
+               if (ret <= 0)
+                       EXIT(E_READ);
+               count += ret;
+       }
+}
+
+/*
+ * set_alsa_params - Prepare the PCM handle for writing
+ *
+ * Install PCM software and hardware configuration. Exit on errors.
+ */
+static void set_alsa_params(void)
+{
+       snd_pcm_hw_params_t *hwparams;
+       snd_pcm_sw_params_t *swparams;
+       snd_pcm_uframes_t buffer_size, xfer_align, start_threshold,
+               stop_threshold;
+       unsigned buffer_time = 0;
+       int err;
+
+       snd_pcm_hw_params_alloca(&hwparams);
+       snd_pcm_sw_params_alloca(&swparams);
+       if (snd_pcm_hw_params_any(handle, hwparams) < 0)
+               EXIT(E_BROKEN_CONF);
+       if (snd_pcm_hw_params_set_access(handle, hwparams,
+                       SND_PCM_ACCESS_RW_INTERLEAVED) < 0)
+               EXIT(E_ACCESS_TYPE);
+       if (snd_pcm_hw_params_set_format(handle, hwparams, FORMAT) < 0)
+               EXIT(E_SAMPLE_FORMAT);
+       if (snd_pcm_hw_params_set_channels(handle, hwparams, conf.channels_arg) < 0)
+               EXIT(E_CHANNEL_COUNT);
+       if (snd_pcm_hw_params_set_rate_near(handle, hwparams, (unsigned int*) &conf.sample_rate_arg, 0) < 0)
+               EXIT(E_SET_RATE);
+       err = snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time, 0);
+       if (err < 0 || !buffer_time)
+               EXIT(E_GET_BUFFER_TIME);
+       if (buffer_time > 500000)
+               buffer_time = 500000;
+       if (snd_pcm_hw_params_set_buffer_time_near(handle, hwparams,
+                       &buffer_time, 0) < 0)
+               EXIT(E_SET_BUFFER_TIME);
+       if (snd_pcm_hw_params(handle, hwparams) < 0)
+               EXIT(E_HW_PARAMS);
+       snd_pcm_hw_params_get_period_size(hwparams, &chunk_size, 0);
+       snd_pcm_hw_params_get_buffer_size(hwparams, &buffer_size);
+       if (chunk_size == buffer_size)
+               EXIT(E_BAD_PERIOD);
+       snd_pcm_sw_params_current(handle, swparams);
+       err = snd_pcm_sw_params_get_xfer_align(swparams, &xfer_align);
+       if (err < 0 || !xfer_align)
+               EXIT(E_GET_XFER);
+       snd_pcm_sw_params_set_sleep_min(handle, swparams, 0);
+       snd_pcm_sw_params_set_avail_min(handle, swparams, chunk_size);
+       /* round to closest transfer boundary */
+       start_threshold = (buffer_size / xfer_align) * xfer_align;
+       if (start_threshold < 1)
+               start_threshold = 1;
+       if (snd_pcm_sw_params_set_start_threshold(handle, swparams,
+                       start_threshold) < 0)
+               EXIT(E_START_THRESHOLD);
+       stop_threshold = buffer_size;
+       if (snd_pcm_sw_params_set_stop_threshold(handle, swparams,
+                       stop_threshold) < 0)
+               EXIT(E_STOP_THRESHOLD);
+       if (snd_pcm_sw_params_set_xfer_align(handle, swparams, xfer_align) < 0)
+               EXIT(E_SET_XFER);
+       if (snd_pcm_sw_params(handle, swparams) < 0)
+               EXIT(E_SW_PARAMS);
+       bytes_per_frame = snd_pcm_format_physical_width(FORMAT) * conf.channels_arg / 8;
+}
+
+/*
+ * pcm_write - push out pcm frames
+ * @data: pointer do data to be written
+ * @count: number of frames
+ *
+ * Return value: Number of bytes written. Exit on errors.
+ */
+static snd_pcm_sframes_t pcm_write(u_char *data, size_t count)
+{
+       snd_pcm_sframes_t r, result = 0;
+#if 0
+       if (count < chunk_size) {
+               snd_pcm_format_set_silence(FORMAT, data
+                       + count * bytes_per_frame,
+                       (chunk_size - count) * conf.channels_arg);
+               count = chunk_size;
+       }
+#endif
+       while (count > 0) {
+               /* write interleaved frames */
+               r = snd_pcm_writei(handle, data, count);
+               if (r == -EAGAIN || (r >= 0 && r < count))
+                       snd_pcm_wait(handle, 1);
+               else if (r == -EPIPE)
+                       snd_pcm_prepare(handle);
+               else if (r < 0)
+                       EXIT(E_WRITE);
+               if (r > 0) {
+                       result += r;
+                       count -= r;
+                       data += r * bytes_per_frame;
+               }
+       }
+       return result;
+}
+
+/*
+ * start_time_in_future - check if current time is later than start_time
+ * @diff: pointer to write remaining time to
+ *
+ * If start_time was not given, or current time is later than given
+ * start_time, return 0. Otherwise, return 1 and write the time
+ * difference between current time and start_time to diff. diff may be
+ * NULL.
+ *
+ */
+static int start_time_in_future(struct timeval *diff)
+{
+       struct timeval now;
+
+       if (!conf.start_time_given)
+               return 0;
+       gettimeofday(&now, NULL);
+       return tv_diff(start_time, &now, diff) > 0? 1 : 0;
+}
+
+/*
+ * do_initial_delay - sleep until time given at command line
+ *
+ * This is called if the initial buffer is filled. It returns
+ * immediately if no start_time was given at the command line
+ * or if the given start time is in the past.
+ *
+ */
+static void do_initial_delay(void)
+{
+       struct timeval diff;
+       int ret;
+
+       fprintf(stderr, "initial delay\n");
+       if (!conf.start_time_given)
+               return;
+again:
+       if (!start_time_in_future(&diff))
+               return;
+       ret = select(1, NULL, NULL, NULL, &diff);
+       if (ret < 0 && errno == EINTR)
+               goto again;
+}
+
+
+/*
+ * play_pcm - play raw pcm data
+ * @l: number of bytes already loaded
+ *
+ * If start_time was given, prebuffer data until buffer is full or
+ * start_time is reached. In any case, do not start playing before
+ * start_time.
+ */
+static void play_pcm(size_t l)
+{
+       ssize_t r, w;
+       unsigned long written = 0;
+       size_t chunk_bytes;
+
+       set_alsa_params();
+       chunk_bytes = chunk_size * bytes_per_frame;
+       audiobuf = realloc(audiobuf, BUFFER_SIZE);
+//     fprintf(stderr, "loaded: %d, chunk_bytes: %d\n", l, chunk_bytes);
+       if (!audiobuf)
+               EXIT(E_MEM);
+       for (;;) {
+               for (;;) {
+                       if (l >= chunk_bytes) {
+                               if (written)
+                                       break;
+                               if (!start_time)
+                                       break;
+                               if (!start_time_in_future(NULL))
+                                       break;
+                               if (l > BUFFER_SIZE) {
+                                       do_initial_delay();
+                                       break;
+                               }
+                       }
+//                     fprintf(stderr, "l = %d, chunk_Bytes = %d\n", l, chunk_bytes);
+                       r = read(STDIN_FILENO, audiobuf + l, BUFFER_SIZE -l);
+                       if (r < 0)
+                               EXIT(E_READ);
+                       l += r;
+//                     fprintf(stderr, "loaded: %d, r= %d\n", l, r);
+                       if (!r)
+                               goto out;;
+               }
+               w = MIN(chunk_bytes, l);
+//             fprintf(stderr, "play: writing %d\n", w);
+               r = (ssize_t) pcm_write(audiobuf, w / bytes_per_frame) * bytes_per_frame;
+//             fprintf(stderr, "wrote %d\n", r);
+               if (r < 0)
+                       EXIT(E_WRITE);
+               written += r;
+               l -= r;
+               if (l)
+                       memmove(audiobuf, audiobuf + r, l);
+//             fprintf(stderr, "written %lu, loaded : %d\n", written, l);
+       }
+out:
+       snd_pcm_drain(handle);
+}
+
+/*
+ * check_wave - test if audio buffer contains a valid wave header
+ *
+ * If not, return 0, otherwise, store number of channels and sample rate
+ * in struct conf and return WAV_HEADER_LEN.
+ */
+static size_t check_wave(void)
+{
+       unsigned char *a = audiobuf;
+       if (a[0] != 'R' || a[1] != 'I' || a[2] != 'F' || a[3] != 'F')
+               return WAV_HEADER_LEN;
+       conf.channels_arg = (unsigned) a[22];
+       conf.sample_rate_arg = a[24] + (a[25] << 8) + (a[26] << 16) + (a[27] << 24);
+//     fprintf(stderr, "channels: %d, rate: %d\n", conf.channels_arg,
+//             conf.sample_rate_arg);
+       return 0;
+}
+
+int main(int argc, char *argv[])
+{
+       snd_pcm_info_t *info;
+       snd_output_t *log;
+       struct timeval tv;
+       int err;
+
+       cmdline_parser(argc, argv, &conf);
+       if (conf.start_time_given) {
+               if (sscanf(conf.start_time_arg, "%lu:%lu",
+                               &tv.tv_sec, &tv.tv_usec) != 2)
+                       EXIT(E_SYNTAX);
+               start_time = &tv;
+       }
+//     fprintf(stderr, "argc=%d, argv[1]=%s\n",argc,  argv[1]);
+       snd_pcm_info_alloca(&info);
+       if (snd_output_stdio_attach(&log, stderr, 0) < 0)
+               EXIT(E_LOG);
+       err = snd_pcm_open(&handle, "plug:swmix",
+               SND_PCM_STREAM_PLAYBACK, 0);
+       if (err < 0)
+               EXIT(E_PCM_OPEN);
+       if ((err = snd_pcm_info(handle, info)) < 0)
+               EXIT(E_SND_PCM_INFO);
+       audiobuf = malloc(WAV_HEADER_LEN);
+       read_wav_header();
+       play_pcm(check_wave());
+       snd_pcm_close(handle);
+       free(audiobuf);
+       snd_output_close(log);
+       snd_config_update_free_global();
+       return EXIT_SUCCESS;
+}
diff --git a/play.ggo b/play.ggo
new file mode 100644 (file)
index 0000000..75fb1ba
--- /dev/null
+++ b/play.ggo
@@ -0,0 +1,5 @@
+section "general options"
+option "start_time" t "start playback at given time which must be in a:b format where a denotes seconds and b denotes microseconds since the epoch" string typestr="timeval" no
+option "device" d "set PCM device" string typestr="device" default="plug:swmix" no
+option "channels" c "number of channels (only neccessary for raw audio)" int typestr="num" default="2" no
+option "sample_rate" s "force given sample rate (only neccessary for raw audio)" int typestr="num" default="44100" no
diff --git a/rc4.h b/rc4.h
new file mode 100644 (file)
index 0000000..c290b48
--- /dev/null
+++ b/rc4.h
@@ -0,0 +1 @@
+#define RC4_KEY_LEN 16
diff --git a/recv.c b/recv.c
new file mode 100644 (file)
index 0000000..aaccb06
--- /dev/null
+++ b/recv.c
@@ -0,0 +1,128 @@
+/*
+ * 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.
+ */
+#include "gcc-compat.h"
+#include "para.h"
+
+#include "recv.h"
+#include "recv.cmdline.h"
+#include "error.h"
+
+struct gengetopt_args_info conf;
+
+INIT_RECV_ERRLISTS;
+
+__printf_2_3 void para_log(int ll, char* fmt,...)
+{
+       va_list argp;
+
+       /* ignore log message if loglevel is not high enough */
+       if (ll < conf.loglevel_arg)
+               return;
+       va_start(argp, fmt);
+       vfprintf(stderr, fmt, argp);
+       va_end(argp);
+}
+
+static void *parse_config(int argc, char *argv[], int *receiver_num)
+{
+       int i;
+
+       if (cmdline_parser(argc, argv, &conf))
+               return NULL;
+       if (conf.list_receivers_given) {
+               printf("available receivers: ");
+               for (i = 0; receivers[i].name; i++)
+                       printf("%s%s", i? " " : "", receivers[i].name);
+               printf("\nTry para_recv -r<receivername>:-h for help on <receivername>\n");
+               exit(EXIT_SUCCESS);
+       }
+       return check_receiver_arg(conf.receiver_arg, receiver_num);
+}
+
+int main(int argc, char *argv[])
+{
+       int ret, eof = 0, max, r_opened = 0, receiver_num;
+       struct timeval timeout;
+       struct  receiver *r = NULL;
+       fd_set rfds, wfds;
+       struct receiver_node rn;
+
+       memset(&rn, 0, sizeof(struct receiver_node));
+       for (ret = 0; receivers[ret].name; ret++)
+               receivers[ret].init(&receivers[ret]);
+       ret = -E_RECV_SYNTAX;
+       rn.conf = parse_config(argc, argv, &receiver_num);
+       if (!rn.conf) {
+               PARA_EMERG_LOG("%s", "parse failed\n");
+               goto out;
+       }
+       r = &receivers[receiver_num];
+       rn.receiver = r;
+       ret = r->open(&rn);
+       if (ret < 0)
+               goto out;
+       r_opened = 1;
+recv:
+       FD_ZERO(&rfds);
+       FD_ZERO(&wfds);
+       timeout.tv_sec = 0;
+       timeout.tv_usec = 1000 * 1000;
+       max = -1;
+       ret = r->pre_select(&rn, &rfds, &wfds, &timeout);
+       max = MAX(max, ret);
+
+       PARA_DEBUG_LOG("timeout: %lums\n", tv2ms(&timeout));
+       ret = select(max + 1, &rfds, &wfds, NULL, &timeout);
+       if (ret < 0) {
+               if (errno == EINTR || errno == EAGAIN)
+                       goto recv;
+               ret = -E_RECV_SELECT;
+               goto out;
+       }
+       ret = r->post_select(&rn, ret, &rfds, &wfds);
+       if (ret < 0)
+               goto out;
+       if (!ret)
+               eof = 1;
+       if (!rn.loaded) {
+               if (eof)
+                       goto out;
+               goto recv;
+       }
+       ret = write(STDOUT_FILENO, rn.buf, rn.loaded);
+       PARA_DEBUG_LOG("wrote %d/%d\n", ret, rn.loaded);
+       if (ret < 0) {
+               ret = -E_WRITE_STDOUT;
+               goto out;
+       }
+       if (ret != rn.loaded) {
+               PARA_INFO_LOG("short write %d/%d\n", ret, rn.loaded);
+               memmove(rn.buf, rn.buf + ret, rn.loaded - ret);
+       }
+       rn.loaded -= ret;
+       if (rn.loaded || !eof)
+               goto recv;
+out:
+       if (r_opened)
+               r->close(&rn);
+       if (r)
+               r->shutdown();
+       if (ret < 0)
+               PARA_NOTICE_LOG("%d: (%s)\n", ret, PARA_STRERROR(-ret));
+       return ret;
+}
diff --git a/recv.ggo b/recv.ggo
new file mode 100644 (file)
index 0000000..b6d5a77
--- /dev/null
+++ b/recv.ggo
@@ -0,0 +1,11 @@
+option "loglevel" l "set loglevel (0-6)" int typestr="level" default="4" no
+option "list_receivers" L "print list of available receivers" flag off
+option "receiver" r "Select receiver.
+
+If options for the selected receiver are given, they must
+be separated by ':' instead of white space. Example:
+
+       -r http:-i:www.paraslash.org:-p:8009
+"
+
+string typestr="receiver_spec" default="http" no
diff --git a/recv.h b/recv.h
new file mode 100644 (file)
index 0000000..3284c29
--- /dev/null
+++ b/recv.h
@@ -0,0 +1,172 @@
+/*
+ * 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 recv.h receiver-relates structures and exported symbols of recv_common.c */
+
+/**
+ * describes one instance of a receiver
+ */
+struct receiver_node {
+       /** points to the corresponding receiver */
+       struct receiver *receiver;
+       /** the output buffer */
+       char *buf;
+       /** the amount of bytes in \a buf */
+       size_t loaded;
+       /** receiver-specific data */
+       void *private_data;
+       /** set to 1 if end of file is reached */
+       int eof;
+       /** pointer to the configuration data for this instance */
+       void *conf;
+};
+
+/**
+ * describes one supported paraslash receiver
+ *
+ * \sa http_recv.c, ortp_recv.c
+ */
+struct receiver {
+/**
+ *
+ *
+ * the name of the receiver
+ */
+       const char *name;
+/**
+ *
+ *
+ * the receiver init function
+ *
+ * It must fill in all other function pointers and is assumed to succeed.
+ *
+ * \sa http_recv_init ortp_recv_init
+ */
+       void (*init)(struct receiver *r);
+/**
+ *
+ *
+ * the command line parser of the receiver
+ *
+ * It should check whether the command line options given by \a argc and \a
+ * argv are valid.  On success, it should return a pointer to the
+ * receiver-specific configuration data determined by \a argc and \a argv.
+ * Note that this might be called more than once with different values of
+ * \a argc and \a argv.
+ *
+ */
+       void * (*parse_config)(int argc, char **argv);
+/**
+ *
+ *
+ * open one instance of the receiver
+ *
+ * This should allocate the output buffer of \a rn. and may also perform any
+ * other work necessary for retrieving the stream according to the
+ * configuration stored in the \a conf member of \a rn which is guaranteed to
+ * point to valid configuration data (as previously obtained from the config
+ * parser).
+ *
+ * \sa receiver_node::conf, receiver_node::buf
+ */
+       int (*open)(struct receiver_node *rn);
+/**
+ *
+ *
+ * close one instance of the receiver
+ *
+ * It should free all resources associated with given receiver node that were
+ * allocated during the corresponding open call.
+ *
+ * \sa receiver_node
+ */
+       void (*close)(struct receiver_node *rn);
+/**
+ *
+ *
+ * deactivate the receiver
+ *
+ * Clean up what init has allocated.
+ */
+       void (*shutdown)(void);
+/**
+ *
+ *
+ * add file descriptors to fd_sets and compute timeout for select(2)
+ *
+ * The pre_select function gets called from the driving application before
+ * entering its select loop. The receiver may use this hook to add any file
+ * descriptors to \a rfds and \a wfds in order to check the result later in the
+ * post_select hook.
+ *
+ * \a timeout is a value-result parameter, initially containing the timeout for
+ * select() which was set by the application or by another receiver node. If
+ * the receiver wants its pre_select function to be called at some earlier time
+ * than the time determined by \a timeout, it may set \a timeout to an
+ * appropriate smaller value. However, it must never increase this timeout.
+ *
+ * This function must return the highest-numbered descriptor it wants to being
+ * checked, or -1 if no file descriptors should be checked for this run.
+ *
+ * \sa select(2), receiver_node:private_data, time.c
+ */
+       int (*pre_select)(struct receiver_node *rn, fd_set *rfds,
+               fd_set *wfds, struct timeval *timeout);
+/**
+ *
+ *
+ * evaluate the result from select()
+ *
+ * If the call to select() was succesful, this hook gets called. It should
+ * check all file descriptors which were added to any of the the fd sets during
+ * the previous call to pre_select. According to the result, it may then use
+ * any non-blocking I/O to establish a connection or to receive the audio data.
+ *
+ * A negative return value is interpreted as an error.
+ *
+ * \sa select(2), struct receiver
+ */
+       int (*post_select)(struct receiver_node *rn, int select_ret,
+               fd_set *rfds, fd_set *wfds);
+};
+
+
+/** \cond */
+extern void http_recv_init(struct receiver *r);
+#define HTTP_RECEIVER {.name = "http", .init = http_recv_init},
+
+#ifdef HAVE_ORTP
+extern void ortp_recv_init(struct receiver *r);
+#define ORTP_RECEIVER {.name = "ortp", .init = ortp_recv_init},
+#else
+#define ORTP_RECEIVER
+#endif
+
+void *check_receiver_arg(char *ra, int *receiver_num);
+
+
+extern struct receiver receivers[];
+extern void (*crypt_function_recv)(unsigned long len, const unsigned char *indata, unsigned char *outdata);
+extern void (*crypt_function_send)(unsigned long len, const unsigned char *indata, unsigned char *outdata);
+
+#define DEFINE_RECEIVER_ARRAY struct receiver receivers[] = { \
+       HTTP_RECEIVER \
+       ORTP_RECEIVER \
+       {.name = NULL}};
+
+/** \endcond */
diff --git a/recv_common.c b/recv_common.c
new file mode 100644 (file)
index 0000000..3a09e7c
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2006 Andre Noll <maan@systemlinux.org>
+ *
+ *     This program is free software; you can redistribute it and/or modify
+ *     it under the terms of the GNU General Public License as published by
+ *     the Free Software Foundation; either version 2 of the License, or
+ *     (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU General Public License for more details.
+ *
+ *     You should have received a copy of the GNU General Public License
+ *     along with this program; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ */
+
+/** \file recv_common.c common functions of para_recv and para_audiod */
+
+#include "para.h"
+
+#include "recv.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;
+
+DEFINE_RECEIVER_ARRAY;
+static void *parse_receiver_args(int receiver_num, char *options)
+{
+       struct receiver *r = &receivers[receiver_num];
+       char **argv;
+       int argc, i;
+       void *conf;
+
+
+//     PARA_DEBUG_LOG("%s, options: %s\n", r->name,
+//             options? options : "(none)");
+       if (options) {
+//             PARA_DEBUG_LOG("%s options: %s\n", name, options);
+               argc = split_args(options, &argv, ':');
+//             PARA_DEBUG_LOG("argc = %d, argv[0]: %s\n", fn->argc, fn->argv[0]);
+               for (i = argc; i >= 0; i--)
+                       argv[i + 1] = argv[i];
+               argc += 2;
+       } else {
+               argc = 1;
+               argv = para_malloc(2 * sizeof(char*));
+               argv[0] = NULL;
+               argv[1] = NULL;
+       }
+       conf = r->parse_config(argc, argv);
+       if (!conf) {
+               for (i = 0; i < argc; i++)
+                       free(argv[i]);
+               free(argv);
+               return NULL;
+       }
+       return conf;
+}
+
+void *check_receiver_arg(char *ra, int *receiver_num)
+{
+       int j;
+
+//     PARA_DEBUG_LOG("checking %s\n", ra);
+       for (j = 0; receivers[j].name; j++) {
+               const char *name = receivers[j].name;
+               size_t len = strlen(name);
+               char c;
+               if (strlen(ra) < len)
+                       continue;
+               if (strncmp(name, ra, len))
+                       continue;
+               c = ra[len];
+               if (c && c != ':')
+                       continue;
+               if (c && !receivers[j].parse_config)
+                       return NULL;
+               *receiver_num = j;
+               return parse_receiver_args(j, c? ra + len + 1: NULL);
+       }
+       PARA_ERROR_LOG("%s", "receiver not found:");
+       return NULL;
+}
diff --git a/ringbuffer.c b/ringbuffer.c
new file mode 100644 (file)
index 0000000..b2ff0c7
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2006 Andre Noll <maan@systemlinux.org>
+ *
+ *     This program is free software; you can redistribute it and/or modify
+ *     it under the terms of the GNU General Public License as published by
+ *     the Free Software Foundation; either version 2 of the License, or
+ *     (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU General Public License for more details.
+ *
+ *     You should have received a copy of the GNU General Public License
+ *     along with this program; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ */
+
+/** \file ringbuffer.c simple ringbuffer implementation */
+
+#include "gcc-compat.h"
+#include "para.h"
+#include "ringbuffer.h"
+#include "string.h"
+
+/**
+ * holds all information about one ring buffer
+ *
+ * It is intentionally not exported via ringbuffer.h. Think abstract.
+ */
+struct ringbuffer
+{
+/**
+ *
+ *
+ * the size of this ring buffer
+*/
+unsigned size;
+/**
+ *
+ *
+ * the actual entries of the ringbuffer
+*/
+void **entries;
+/**
+ *
+ *
+ * the next entry will be added at this position
+ */
+int head;
+/**
+ *
+ * how many entries the ring buffer contains
+*/
+unsigned filled;
+};
+
+/**
+ * initialize a new ringbuffer
+ *
+ * @param size the number of entries the ringbuffer holds
+ *
+ * This function initializes a circular ring buffer which can hold up to \a
+ * size entries of arbitrary type. If performance is an issue, \a size should
+ * be a power of two to make the underlying modulo operations cheap. Arbitrary
+ * many ringbuffers may be initialized via this function. Each ringbuffer is
+ * identified by a 'cookie'.
+ *
+ * Return value: A 'cookie' which identifies the ringbuffer just created and
+ * which must be passed to ringbuffer_add() and ringbuffer_get().
+ */
+void *ringbuffer_new(unsigned size)
+{
+       struct ringbuffer *rb = para_calloc(sizeof(struct ringbuffer));
+       rb->entries = para_calloc(size * sizeof(void *));
+       rb->size = size;
+       return rb;
+};
+
+/**
+ * add one entry to a ringbuffer
+ *
+ * @param cookie the ringbuffer identifier
+ * @param data the data to be inserted
+ *
+ * insert \a data into the ringbuffer associated with \a cookie.  As soon as
+ * the ringbuffer fills up, its oldest entry is disregarded and replaced by \a
+ * data.
+ *
+ * \return The old \a data pointer which is going to be disregarded, or
+ * NULL if the ringbuffer is not yet full.
+ */
+void *ringbuffer_add(void *cookie, void *data)
+{
+       struct ringbuffer *rb = cookie;
+       void *ret = rb->entries[rb->head];
+       rb->entries[rb->head] = data;
+       rb->head = (rb->head + 1) % rb->size;
+       if (rb->filled < rb->size)
+               rb->filled++;
+       return ret;
+}
+
+/**
+ * get one entry from a ringbuffer
+ *
+ * @param cookie the ringbuffer identifier
+ * @param num the number of the entry
+ *
+ * \return A pointer to data previously added, or NULL if entry number
+ * \a num is not available. \a num counts backwards from zero, i.e.
+ * ringbuffer_get_entry(0) gets the entry which was added most recently.
+ */
+void *ringbuffer_get(void *cookie, int num)
+{
+       struct ringbuffer *rb = cookie;
+       int pos = (rb->head + rb->size - 1 - num) % rb->size;
+//     fprintf(stderr, "pos = %d\n", pos);
+       return rb->entries[pos];
+}
+
+/**
+ * get the number of entries in the ring buffer
+ *
+ * @param cookie the ringbuffer identifier
+ *
+ * This function always succeeds and never returns a number greater than the
+ * size of the ring buffer.
+ */
+unsigned ringbuffer_filled(void *cookie)
+{
+       struct ringbuffer *rb = cookie;
+       return rb->filled;
+}
diff --git a/ringbuffer.h b/ringbuffer.h
new file mode 100644 (file)
index 0000000..c723934
--- /dev/null
@@ -0,0 +1,6 @@
+/** \file ringbuffer.h exported symbols from ringbuffer.c */
+void *ringbuffer_new(unsigned size);
+void *ringbuffer_add(void *cookie, void *data);
+void *ringbuffer_get(void *cookie, int num);
+unsigned ringbuffer_filled(void *cookie);
+
diff --git a/scripts/demo-script b/scripts/demo-script
new file mode 100755 (executable)
index 0000000..003aa7b
--- /dev/null
@@ -0,0 +1,56 @@
+#!/bin/bash
+
+bin="para_client para_audioc para_audiod para_gui" # the binaries we need
+log="`pwd`/demo-log.$$.log"
+dir="$HOME/.paraslash"
+client_conf="$dir/client.conf"
+audioc_conf="$dir/audioc.conf"
+server=www.paraslash.org
+proj=paraslash-0.2.10
+df=$proj.tar.bz2 # download file
+url=http://$server/versions/$df
+kf="$dir/key.anonymous" # key file
+key_url=http://$server/key.anonymous
+socket="$dir/socket"
+receiver_opts="mp3:http:-i:$server:-p:8009"
+audiod_log="$dir/audiod.log"
+audiod_opts="-FDdr $receiver_opts -L $audiod_log -s $socket"
+msg()
+{
+       echo "`date`: $1"
+}
+
+go()
+{
+       msg "downloading $df"
+       wget -N -q $url || return 1
+       msg "decompressing"
+       rm -rf $proj; tar xjf $df
+       cd $proj || return 1
+       msg "configuring (be patient, ignore warnings but not errors)"
+       ./configure --prefix "$HOME" >> "$log"
+       msg "building $bin"
+       make $bin >> "$log"
+       msg "installing"
+       mkdir -p $HOME/bin && install -c -s -m 755 $bin $HOME/bin || return 1
+       export PATH=$HOME/bin:$PATH
+       msg "retrieving anonymous key"
+       mkdir -p $dir || return 1
+       wget -q --directory-prefix=$dir $key_url || return 1
+       msg "writing $client_conf"
+       cat << EOF > "$client_conf"
+               user "anonymous"
+               hostname "$server"
+               key_file "$kf"
+EOF
+       msg "writing $audioc_conf"
+       echo "socket \"$socket\"" > "$audioc_conf"
+       (para_audioc term; killall para_audiod para_client) >> "$log" 2>&1
+       msg "para_audiod $audiod_opts"
+       para_audiod $audiod_opts -w "mp3:mpg123 -"
+       echo "hit return to start para_gui, hit ctrl+c to abort"
+       read
+       para_gui
+}
+
+go
diff --git a/sdl_gui.c b/sdl_gui.c
new file mode 100644 (file)
index 0000000..bdc809b
--- /dev/null
+++ b/sdl_gui.c
@@ -0,0 +1,840 @@
+/*
+ * Copyright (C) 2003-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 sdl_gui.c SDL-based interface for paraslash */
+
+#include "para.h"
+#include "string.h"
+
+
+#include <SDL/SDL.h>
+#include "SFont.h"
+
+#include <SDL/SDL_image.h>
+#include <SDL/SDL_events.h>
+#include <SDL/SDL_keyboard.h>
+#include <SDL/SDL_keysym.h>
+#include <sys/time.h> /* timeval, needed for select */
+
+#include "sdl_gui.cmdline.h"
+
+#define FRAME_RED 100
+#define FRAME_GREEN 200
+#define FRAME_BLUE 200
+
+SDL_Surface *screen;
+static int width = 0;
+static int height = 0;
+
+extern const char *status_item_list[NUM_STAT_ITEMS];
+struct gengetopt_args_info args_info;
+
+#define FRAME_WIDTH 10
+#define FONT_HEIGHT 36
+
+#define INPUT_WIDTH width - 2 * FRAME_WIDTH
+#define INPUT_X FRAME_WIDTH
+#define INPUT_HEIGHT (args_info.interactive_flag? FONT_HEIGHT : 0)
+#define INPUT_Y height - FRAME_WIDTH - INPUT_HEIGHT
+
+#define OUTPUT_WIDTH width - 2 * FRAME_WIDTH
+#define OUTPUT_X FRAME_WIDTH
+#define OUTPUT_Y FRAME_WIDTH
+#define OUTPUT_HEIGHT (height - INPUT_HEIGHT - 2 * FRAME_WIDTH)
+
+#define NUM_LINES (height - 2 * FRAME_WIDTH - INPUT_HEIGHT) / FONT_HEIGHT
+
+#define M_YELLOW 0
+#define N_YELLOW 1
+
+#define LEFT 1
+#define RIGHT 2
+#define CENTER 3
+
+
+struct stat_item{
+        char *name;
+        char *prefix;
+        char *postfix;
+        char *content;
+        unsigned x;
+        unsigned y;
+        unsigned w;
+        unsigned h;
+       Uint8 r;
+       Uint8 g;
+       Uint8 b;
+        int font;
+        int align;
+};
+
+struct font {
+       char name[MAXLINE];
+        SFont_FontInfo fontinfo;
+};
+
+struct font fonts[] = {
+       {
+               .name = "24P_Arial_Metallic_Yellow.png",
+               .fontinfo = {NULL, {0}, 0}
+       },
+       {
+               .name = "24P_Arial_NeonYellow.png",
+               .fontinfo = {NULL, {0}, 0}
+       },
+       {
+               .name = "24P_Copperplate_Blue.png",
+               .fontinfo = {NULL, {0}, 0}
+       },
+       {
+               .name = "",
+               .fontinfo = {NULL, {0}, 0}
+       }
+};
+
+
+#define PIC_WIDTH width * 3 / 10
+#define PIC_HEIGHT height / 3
+
+#define INPUT_FONT 0
+#define OUTPUT_FONT 1
+#define MSG_FONT 2
+
+
+static struct stat_item stat_items[NUM_STAT_ITEMS];
+
+void para_log(__unused int ll, __unused char* fmt,...) /* no logging */
+{
+}
+
+static void init_stat_items(void)
+{
+       int i;
+       struct stat_item *s = stat_items;
+
+       for (i = 0; i < NUM_STAT_ITEMS; i++) {
+               s[i].w = 0;
+               s[i].content = NULL;
+       }
+
+
+       s[SI_STATUS_BAR].prefix = "";
+       s[SI_STATUS_BAR].postfix = "";
+       s[SI_STATUS_BAR].content = "";
+       s[SI_STATUS_BAR].x = 0;
+       s[SI_STATUS_BAR].y = 10;
+       s[SI_STATUS_BAR].w = 100;
+       s[SI_STATUS_BAR].h = FONT_HEIGHT;
+       s[SI_STATUS_BAR].r = 0;
+       s[SI_STATUS_BAR].g = 0;
+       s[SI_STATUS_BAR].b = 0;
+       s[SI_STATUS_BAR].font = M_YELLOW;
+       s[SI_STATUS_BAR].align = CENTER;
+
+       s[SI_PLAY_TIME].prefix = "";
+       s[SI_PLAY_TIME].postfix = "";
+       s[SI_PLAY_TIME].content = "";
+       s[SI_PLAY_TIME].x = 35;
+       s[SI_PLAY_TIME].y = 20;
+       s[SI_PLAY_TIME].w = 65;
+       s[SI_PLAY_TIME].h = FONT_HEIGHT;
+       s[SI_PLAY_TIME].r = 0;
+       s[SI_PLAY_TIME].g = 0;
+       s[SI_PLAY_TIME].b = 0;
+       s[SI_PLAY_TIME].font = M_YELLOW;
+       s[SI_PLAY_TIME].align = CENTER;
+
+       s[SI_STATUS].prefix = "";
+       s[SI_STATUS].postfix = "";
+       s[SI_STATUS].content = "";
+       s[SI_STATUS].x = 35;
+       s[SI_STATUS].y = 28;
+       s[SI_STATUS].w = 12;
+       s[SI_STATUS].h = FONT_HEIGHT;
+       s[SI_STATUS].r = 0;
+       s[SI_STATUS].g = 0;
+       s[SI_STATUS].b = 0;
+       s[SI_STATUS].font = N_YELLOW;
+       s[SI_STATUS].align = LEFT;
+
+       s[SI_STATUS_FLAGS].prefix = " (";
+       s[SI_STATUS_FLAGS].postfix = ")";
+       s[SI_STATUS_FLAGS].content = "";
+       s[SI_STATUS_FLAGS].x = 47;
+       s[SI_STATUS_FLAGS].y = 28;
+       s[SI_STATUS_FLAGS].w = 15;
+       s[SI_STATUS_FLAGS].h = FONT_HEIGHT;
+       s[SI_STATUS_FLAGS].r = 0;
+       s[SI_STATUS_FLAGS].g = 0;
+       s[SI_STATUS_FLAGS].b = 0;
+       s[SI_STATUS_FLAGS].font = N_YELLOW;
+       s[SI_STATUS_FLAGS].align = CENTER;
+
+       s[SI_NUM_PLAYED].prefix = "#";
+       s[SI_NUM_PLAYED].postfix = "";
+       s[SI_NUM_PLAYED].content = "0";
+       s[SI_NUM_PLAYED].x = 62;
+       s[SI_NUM_PLAYED].y = 28;
+       s[SI_NUM_PLAYED].w = 13;
+       s[SI_NUM_PLAYED].h = FONT_HEIGHT;
+       s[SI_NUM_PLAYED].r = 0;
+       s[SI_NUM_PLAYED].g = 0;
+       s[SI_NUM_PLAYED].b = 0;
+       s[SI_NUM_PLAYED].font = N_YELLOW;
+       s[SI_NUM_PLAYED].align = CENTER;
+
+       s[SI_UPTIME].prefix = "Up: ";
+       s[SI_UPTIME].postfix = "";
+       s[SI_UPTIME].content = "";
+       s[SI_UPTIME].x = 75;
+       s[SI_UPTIME].y = 28;
+       s[SI_UPTIME].w = 25;
+       s[SI_UPTIME].h = FONT_HEIGHT;
+       s[SI_UPTIME].r = 0;
+       s[SI_UPTIME].g = 0;
+       s[SI_UPTIME].b = 0;
+       s[SI_UPTIME].font = N_YELLOW;
+       s[SI_UPTIME].align = RIGHT;
+
+       s[SI_DBTOOL].prefix = "dbtool: ";
+       s[SI_DBTOOL].postfix = "";
+       s[SI_DBTOOL].content = "no content yet";
+       s[SI_DBTOOL].x = 35;
+       s[SI_DBTOOL].y = 48;
+       s[SI_DBTOOL].w = 35;
+       s[SI_DBTOOL].h = FONT_HEIGHT;
+       s[SI_DBTOOL].r = 0;
+       s[SI_DBTOOL].g = 0;
+       s[SI_DBTOOL].b = 0;
+       s[SI_DBTOOL].font = N_YELLOW;
+       s[SI_DBTOOL].align = LEFT;
+
+       s[SI_FORMAT].prefix = "Format: ";
+       s[SI_FORMAT].postfix = "";
+       s[SI_FORMAT].content = "";
+       s[SI_FORMAT].x = 70;
+       s[SI_FORMAT].y = 48;
+       s[SI_FORMAT].w = 30;
+       s[SI_FORMAT].h = FONT_HEIGHT;
+       s[SI_FORMAT].r = 0;
+       s[SI_FORMAT].g = 0;
+       s[SI_FORMAT].b = 0;
+       s[SI_FORMAT].font = N_YELLOW;
+       s[SI_FORMAT].align = RIGHT;
+
+       s[SI_MTIME].prefix = "MTime: ";
+       s[SI_MTIME].postfix = "";
+       s[SI_MTIME].content = "";
+       s[SI_MTIME].x = 35;
+       s[SI_MTIME].y = 35;
+       s[SI_MTIME].w = 65;
+       s[SI_MTIME].h = FONT_HEIGHT;
+       s[SI_MTIME].r = 0;
+       s[SI_MTIME].g = 0;
+       s[SI_MTIME].b = 0;
+       s[SI_MTIME].font = N_YELLOW;
+       s[SI_MTIME].align = LEFT;
+
+       s[SI_FILE_SIZE].prefix = "Size: ";
+       s[SI_FILE_SIZE].postfix = "kb";
+       s[SI_FILE_SIZE].content = "";
+       s[SI_FILE_SIZE].x = 35;
+       s[SI_FILE_SIZE].y = 42;
+       s[SI_FILE_SIZE].w = 20;
+       s[SI_FILE_SIZE].h = FONT_HEIGHT;
+       s[SI_FILE_SIZE].r = 0;
+       s[SI_FILE_SIZE].g = 0;
+       s[SI_FILE_SIZE].b = 0;
+       s[SI_FILE_SIZE].font = N_YELLOW;
+       s[SI_FILE_SIZE].align = LEFT;
+
+       s[SI_AUDIO_INFO1].prefix = "";
+       s[SI_AUDIO_INFO1].postfix = "";
+       s[SI_AUDIO_INFO1].content = "";
+       s[SI_AUDIO_INFO1].x = 0;
+       s[SI_AUDIO_INFO1].y = 60;
+       s[SI_AUDIO_INFO1].w = 100;
+       s[SI_AUDIO_INFO1].h = FONT_HEIGHT;
+       s[SI_AUDIO_INFO1].r = 0;
+       s[SI_AUDIO_INFO1].g = 0;
+       s[SI_AUDIO_INFO1].b = 0;
+       s[SI_AUDIO_INFO1].font = N_YELLOW;
+       s[SI_AUDIO_INFO1].align = CENTER;
+
+       s[SI_AUDIO_INFO2].prefix = "";
+       s[SI_AUDIO_INFO2].postfix = "";
+       s[SI_AUDIO_INFO2].content = "";
+       s[SI_AUDIO_INFO2].x = 0;
+       s[SI_AUDIO_INFO2].y = 65;
+       s[SI_AUDIO_INFO2].w = 100;
+       s[SI_AUDIO_INFO2].h = FONT_HEIGHT;
+       s[SI_AUDIO_INFO2].r = 0;
+       s[SI_AUDIO_INFO2].g = 0;
+       s[SI_AUDIO_INFO2].b = 0;
+       s[SI_AUDIO_INFO2].font = N_YELLOW;
+       s[SI_AUDIO_INFO2].align = CENTER;
+
+       s[SI_AUDIO_INFO3].prefix = "";
+       s[SI_AUDIO_INFO3].postfix = "";
+       s[SI_AUDIO_INFO3].content = "";
+       s[SI_AUDIO_INFO3].x = 0;
+       s[SI_AUDIO_INFO3].y = 70;
+       s[SI_AUDIO_INFO3].w = 100;
+       s[SI_AUDIO_INFO3].h = FONT_HEIGHT;
+       s[SI_AUDIO_INFO3].r = 0;
+       s[SI_AUDIO_INFO3].g = 0;
+       s[SI_AUDIO_INFO3].b = 0;
+       s[SI_AUDIO_INFO3].font = N_YELLOW;
+       s[SI_AUDIO_INFO3].align = CENTER;
+
+       s[SI_DBINFO1].name = "dbinfo1:";
+       s[SI_DBINFO1].prefix = "";
+       s[SI_DBINFO1].postfix = "";
+       s[SI_DBINFO1].content = "";
+       s[SI_DBINFO1].x = 0;
+       s[SI_DBINFO1].y = 83;
+       s[SI_DBINFO1].w = 100;
+       s[SI_DBINFO1].h = FONT_HEIGHT;
+       s[SI_DBINFO1].r = 0;
+       s[SI_DBINFO1].g = 0;
+       s[SI_DBINFO1].b = 0;
+       s[SI_DBINFO1].font = N_YELLOW;
+       s[SI_DBINFO1].align = CENTER;
+
+       s[SI_DBINFO2].prefix = "";
+       s[SI_DBINFO2].postfix = "";
+       s[SI_DBINFO2].content = "";
+       s[SI_DBINFO2].x = 0;
+       s[SI_DBINFO2].y = 88;
+       s[SI_DBINFO2].w = 100;
+       s[SI_DBINFO2].h = FONT_HEIGHT;
+       s[SI_DBINFO2].r = 0;
+       s[SI_DBINFO2].g = 0;
+       s[SI_DBINFO2].b = 0;
+       s[SI_DBINFO2].font = N_YELLOW;
+       s[SI_DBINFO2].align = CENTER;
+
+       s[SI_DBINFO3].name = "dbinfo3:";
+       s[SI_DBINFO3].prefix = "";
+       s[SI_DBINFO3].postfix = "";
+       s[SI_DBINFO3].content = "";
+       s[SI_DBINFO3].x = 0;
+       s[SI_DBINFO3].y = 93;
+       s[SI_DBINFO3].w = 100;
+       s[SI_DBINFO3].h = FONT_HEIGHT;
+       s[SI_DBINFO3].r = 0;
+       s[SI_DBINFO3].g = 0;
+       s[SI_DBINFO3].b = 0;
+       s[SI_DBINFO3].font = N_YELLOW;
+       s[SI_DBINFO3].align = CENTER;
+}
+
+/*
+ * init SDL libary and set window title
+ */
+static void init_SDL(void)
+{
+       if (SDL_Init(SDL_INIT_VIDEO) == -1) {
+               fprintf(stderr,
+                       "Couldn't initialize SDL: %s\n", SDL_GetError());
+               exit(1);
+       }
+       /* Clean up on exit */
+       atexit(SDL_Quit);
+       /* Initialize the display */
+       if (args_info.fullscreen_flag)
+               screen = SDL_SetVideoMode(width, height, 0, SDL_FULLSCREEN);
+       else
+               screen = SDL_SetVideoMode(width, height, 0, 0);
+       if (!screen) {
+               fprintf(stderr, "Couldn't set video mode: %s\n",
+                       SDL_GetError());
+               exit(1);
+       }
+       SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE);
+       SDL_EventState(SDL_MOUSEBUTTONDOWN, SDL_IGNORE);
+       SDL_EventState(SDL_MOUSEBUTTONUP, SDL_IGNORE);
+       /* Set the window manager title bar */
+       SDL_WM_SetCaption("The Gui of death that makes you blind (paraslash "
+               VERSION ")", "SFont");
+}
+
+/*
+ * draw rectangular frame of width FRAME_WIDTH
+ */
+static void draw_frame(Uint8 r, Uint8 g, Uint8 b) {
+       SDL_Rect rect;
+
+       rect.x = 0;
+       rect.y = 0;
+       rect.w = width;
+       rect.h = FRAME_WIDTH;
+       SDL_FillRect(screen, &rect, SDL_MapRGB(screen->format, r, g, b));
+       SDL_UpdateRect(screen, rect.x, rect.y, rect.w, rect.h);
+
+       rect.x = 0;
+       rect.y = height - FRAME_WIDTH;
+       rect.w = width;
+       rect.h = FRAME_WIDTH;
+       SDL_FillRect(screen, &rect, SDL_MapRGB(screen->format, r, g, b));
+       SDL_UpdateRect(screen, rect.x, rect.y, rect.w, rect.h);
+
+       rect.x = 0;
+       rect.y = FRAME_WIDTH;
+       rect.w = FRAME_WIDTH;
+       rect.h = height -  2 * FRAME_WIDTH;
+       SDL_FillRect(screen, &rect, SDL_MapRGB(screen->format, r, g, b));
+       SDL_UpdateRect(screen, rect.x, rect.y, rect.w, rect.h);
+
+       rect.x = width - FRAME_WIDTH;
+       rect.y = FRAME_WIDTH;
+       rect.w = FRAME_WIDTH;
+       rect.h = height -  2 * FRAME_WIDTH;
+       SDL_FillRect(screen, &rect, SDL_MapRGB(screen->format, r, g, b));
+       SDL_UpdateRect(screen, rect.x, rect.y, rect.w, rect.h);
+}
+
+/*
+ * fill input rect with color
+ */
+static void fill_input_rect(void)
+{
+       SDL_Rect rect;
+
+       rect.x = INPUT_X;
+       rect.y = INPUT_Y;
+       rect.w = INPUT_WIDTH;
+       rect.h = INPUT_HEIGHT;
+       SDL_FillRect(screen, &rect, SDL_MapRGB(screen->format, 10, 150, 10));
+}
+
+/*
+ * fill output rect with color
+ */
+static void fill_output_rect(void)
+{
+       SDL_Rect rect;
+
+       rect.x = OUTPUT_X;
+       rect.y = OUTPUT_Y;
+       rect.w = OUTPUT_WIDTH;
+       rect.h = OUTPUT_HEIGHT;
+       SDL_FillRect(screen, &rect, SDL_MapRGB(screen->format, 0, 0, 0));
+}
+
+/*
+ * convert tab to space
+ */
+static void tab2space(char *text)
+{
+       char *p = text;
+       while (*p) {
+               if (*p == '\t')
+                       *p = ' ';
+               p++;
+       }
+}
+
+static void print_msg(char *msg)
+{
+       SFont_FontInfo *font = &(fonts[MSG_FONT].fontinfo);
+       char *buf = strdup(msg);
+       int len = strlen(buf);
+
+       if (!buf)
+               return;
+       while (TextWidth2(font, buf) > INPUT_WIDTH && len > 0) {
+               *(buf + len) = '\0';
+               len--;
+       }
+       fill_input_rect();
+       PutString2(screen, font, INPUT_X, INPUT_Y, buf);
+       free(buf);
+}
+
+static void update_all(void)
+{
+       SDL_UpdateRect(screen, 0, 0, 0, 0);
+}
+
+static void update_input(void)
+{
+       SDL_UpdateRect(screen, INPUT_X, INPUT_Y, INPUT_WIDTH, INPUT_HEIGHT);
+
+}
+
+/*
+ * wait for key, ignore all other events, return 0 if there is no key event
+ * pending. Otherwise return keysym of key
+ */
+SDLKey get_key(void)
+{
+       SDL_Event event;
+
+       while (SDL_PollEvent(&event) > 0) {
+               if(event.type != SDL_KEYDOWN)
+                       continue;
+//             printf("Key pressed, scancode: 0x%x\n",
+//                     event.key.keysym.scancode);
+               return event.key.keysym.sym;
+       }
+       return 0;
+}
+
+/*
+ * print message, wait for key (blocking), return 1 for 'q', 0 else
+ */
+static SDLKey hit_key(char *msg)
+{
+       SDLKey sym;
+
+       print_msg(msg);
+       update_input();
+       while (!(sym = get_key()))
+               ;
+       fill_input_rect();
+       update_input();
+       if (sym == SDLK_q)
+               return 1;
+       else
+               return 0;
+}
+
+/*
+ * read paraslash command from input, execute it and print results
+ */
+static int command_handler(void)
+{
+       FILE *pipe;
+       unsigned count = 0;
+       char text[MAXLINE]="";
+       char buf[MAXLINE]="";
+       SFont_FontInfo *font = &fonts[OUTPUT_FONT].fontinfo;
+
+//     printf("string input\n");
+       SFont_Input2(screen, &fonts[INPUT_FONT].fontinfo,
+               INPUT_X, INPUT_Y - 5, INPUT_WIDTH, text);
+       if (!strlen(text))
+               return 1;
+       if (!strcmp(text, "exit") || !strcmp(text, "quit"))
+               return 0;
+       if (text[0] == '!') {
+               if (text[1] == '\0')
+                       return 1;
+               pipe = popen(text + 1, "r");
+       } else {
+               sprintf(buf, BINDIR "/para_client %s 2>&1", text);
+               pipe = popen(buf, "r");
+       }
+       if (!pipe)
+               return 0;
+       fill_output_rect();
+       while(fgets(text, MAXLINE - 1, pipe)) {
+               int len;
+
+               tab2space(text);
+               len = strlen(text);
+       //      printf("string: %s\n", dest);
+               while (TextWidth2(font, text) > width - 2 * FRAME_WIDTH &&
+                               len > 0) {
+                       text[len] = '\0';
+                       len--;
+               }
+               PutString2(screen, font, OUTPUT_X,
+                       OUTPUT_Y + count * FONT_HEIGHT, text);
+               count++;
+               if (count >= NUM_LINES) {
+                       update_all();
+                       if (hit_key("Hit any key to continue, q to return"))
+                               goto out;
+                       count = 0;
+                       fill_output_rect();
+               }
+       }
+       update_all();
+       hit_key("Hit any key to return");
+out:   fill_output_rect();
+       pclose(pipe);
+       return 1;
+}
+
+
+/*
+ * Add prefix and postfix to string, delete characters from the end
+ * if its length exceeds the max length defined in stat_items[item]
+ */
+char *transform_string(int item)
+{
+       struct stat_item s = stat_items[item];
+       size_t len;
+       char *ret;
+       unsigned pixels = s.w * (width - 2 * FRAME_WIDTH) / 100;
+       SFont_FontInfo *font = &(fonts[s.font].fontinfo);
+
+       ret = make_message("%s%s%s", s.prefix, s.content, s.postfix);
+       len = strlen(ret);
+       while (TextWidth2(font, ret) > pixels && len > 0) {
+               *(ret + len) = '\0';
+               len--;
+       }
+       return ret;
+}
+
+SDL_Surface *load_jpg(void)
+{
+       SDL_RWops *rwop;
+       int fds[3] = {0, 1, 0};
+       pid_t pid;
+       FILE *pipe;
+
+       if (para_exec_cmdline_pid(&pid, args_info.pic_cmd_arg, fds) < 0)
+               return NULL;
+       pipe = fdopen(fds[1], "r");
+       if (!pipe)
+               return NULL;
+       if (!(rwop = SDL_RWFromFP(pipe, 0)))
+               return NULL;
+       return IMG_LoadJPG_RW(rwop);
+}
+
+void update_pic(void)
+{
+       SDL_Surface *img;
+       SDL_Rect src_pic_rect = {
+               .x = 0,
+               .y = 0,
+               .w = PIC_WIDTH,
+               .h = PIC_HEIGHT,
+       };
+       SDL_Rect dest_pic_rect = {
+               .x = FRAME_WIDTH,
+               .y = OUTPUT_HEIGHT / 5,
+               .w = PIC_WIDTH,
+               .h = PIC_HEIGHT,
+       };
+
+       if (!screen)
+               return;
+
+       if (!(img = load_jpg()))
+               return;
+       SDL_FillRect(screen, &dest_pic_rect, SDL_MapRGB(screen->format,
+               0, 0, 0));
+       SDL_BlitSurface(img, &src_pic_rect, screen, &dest_pic_rect);
+       SDL_Flip(screen);
+       SDL_FreeSurface(img);
+}
+
+/*
+ * update status item number i.
+ */
+static void do_update(int i)
+{
+       static int last_played = -1;
+       SDL_Rect rect;
+       char *buf;
+       SFont_FontInfo *font = &(fonts[stat_items[i].font].fontinfo);
+       if (!stat_items[i].w)
+               return;
+
+       rect.x = stat_items[i].x * (width - FRAME_WIDTH * 2) / 100
+               + FRAME_WIDTH;
+       rect.y = stat_items[i].y * (height - 2 * FRAME_WIDTH - INPUT_HEIGHT)
+               / 100;
+       rect.w = stat_items[i].w * (width - 2 * FRAME_WIDTH) / 100;
+       rect.h = stat_items[i].h;
+       buf = transform_string(i);
+       SDL_FillRect(screen, &rect, SDL_MapRGB(screen->format,
+               stat_items[i].r, stat_items[i].g, stat_items[i].b));
+       switch(stat_items[i].align) {
+       case CENTER:
+               PutString2(screen, font,
+                       rect.x + (rect.w - TextWidth2(font, buf)) / 2,
+                       rect.y, buf);
+               break;
+       case LEFT:
+               PutString2(screen, font, rect.x, rect.y, buf);
+               break;
+       case RIGHT:
+               PutString2(screen, font, rect.x + (rect.w -
+                       TextWidth2(font, buf)), rect.y, buf);
+               break;
+       }
+       free(buf);
+       SDL_UpdateRect(screen, rect.x, rect.y, rect.w, rect.h);
+       if (i == SI_NUM_PLAYED && atoi(stat_items[i].content) != last_played) {
+               update_pic();
+               last_played = atoi(stat_items[i].content);
+       };
+}
+
+/*
+ * Check if buf is a known status line. If so call do_update and return 1.
+ * Return 0 otherwise.
+ */
+void update_status(char *buf)
+{
+       int i;
+
+       i = stat_line_valid(buf);
+       if (i < 0)
+               return;
+       //free(stat_items[i].content);
+       stat_items[i].content = para_strdup(buf +
+               strlen(status_item_list[i]) + 1);
+       do_update(i);
+}
+
+/*
+ * Read stat line from pipe if pipe is ready, call update_status to
+ * display information.
+ */
+static int draw_status(int pipe)
+{
+       fd_set rfds;
+       int ret;
+       struct timeval tv;
+
+       tv.tv_sec = 0;
+       tv.tv_usec = 3000000;
+       FD_ZERO(&rfds);
+       FD_SET(pipe, &rfds);
+       ret = select(pipe + 1, &rfds, NULL, NULL, &tv);
+//     printf("select returned %d\n", ret);
+       if (ret <= 0)
+               return 0;
+       if (read_audiod_pipe(pipe, update_status) > 0)
+               return 1;
+//     clear_all_items();
+       free(stat_items[SI_STATUS_BAR].content);
+       stat_items[SI_STATUS_BAR].content =
+               para_strdup("audiod not running!?\n");
+       update_all();
+       sleep(1);
+       return -1;
+}
+
+static void clean_exit(int ret)
+{
+       SDL_Quit();
+       exit(ret);
+}
+
+static void print_help(void)
+{
+       print_msg("Hit q to quit, any other key to enter command mode");
+}
+
+static int configfile_exists(void)
+{
+       if (!args_info.config_file_given) {
+               char *home = para_homedir();
+               args_info.config_file_arg = make_message(
+                       "%s/.paraslash/sdl_gui.conf", home);
+               free(home);
+       }
+       return file_exists(args_info.config_file_arg);
+}
+
+/*
+ * MAIN
+ */
+int main(int argc, char *argv[])
+{
+       int i, ret, pipe;
+       SDLKey sym;
+
+       cmdline_parser(argc, argv, &args_info);
+       ret = configfile_exists();
+//     printf("w=%i,h=%i,ret=%i, cf=%s\n", width, height, ret, args_info.config_file_arg);
+
+       if (!ret && args_info.config_file_given) {
+               fprintf(stderr, "Can't read config file %s\n",
+                       args_info.config_file_arg);
+               exit(EXIT_FAILURE);
+       }
+       if (ret)
+               cmdline_parser_configfile(args_info.config_file_arg,
+                       &args_info, 0, 0, 0);
+       signal(SIGCHLD, SIG_IGN);
+       width = args_info.width_arg;
+       height = args_info.height_arg;
+//     printf("w=%i,h=%i,ret=%i, cf=%s\n", width, height, ret, args_info.config_file_arg);
+       init_stat_items();
+       pipe = para_open_audiod_pipe(args_info.stat_cmd_arg);
+       init_SDL();
+       for (i = 0; fonts[i].name[0]; i++) {
+               char buf[MAXLINE];
+               sprintf(buf, "%s/%s", FONTDIR, fonts[i].name);
+               /* Load the font - You don't have to use the IMGlib for this */
+               fonts[i].fontinfo.Surface = IMG_Load(buf);
+               /* Prepare the font for use */
+               InitFont2(&fonts[i].fontinfo);
+       }
+       draw_frame(FRAME_RED, FRAME_GREEN, FRAME_BLUE);
+       if (args_info.interactive_flag) {
+               print_help();
+               update_input();
+       }
+       for (;;) {
+               ret = draw_status(pipe);
+               if (ret < 0) {
+                       close(pipe);
+                       pipe = -1;
+               }
+               if (SDL_QuitRequested())
+                       clean_exit(0);
+               while ((sym = get_key())) {
+                       if (!args_info.interactive_flag)
+                               clean_exit(0);
+                       if (sym == SDLK_q)
+                               clean_exit(0);
+                       if (       sym == SDLK_LSHIFT
+                               || sym == SDLK_RSHIFT
+                               || sym == SDLK_LMETA
+                               || sym == SDLK_RMETA
+                               || sym == SDLK_RCTRL
+                               || sym == SDLK_LCTRL
+                               || sym == SDLK_MODE
+                               || sym == SDLK_CAPSLOCK
+                               || sym == SDLK_LALT
+                               || sym == SDLK_RALT
+                               || sym == SDLK_RSUPER
+                               || sym == SDLK_LSUPER
+                               || sym == SDLK_COMPOSE
+                               )
+                               continue;
+                       if (pipe < 0) {
+//                             printf("closing pipe\n");
+                               kill(0, SIGINT);
+                               close(pipe);
+//                             printf("pipe closed\n");
+                       }
+                       fill_input_rect();
+                       update_input();
+                       if (!command_handler())
+                               clean_exit(0);
+                       fill_output_rect();
+                       print_help();
+                       update_pic();
+                       SDL_UpdateRect(screen, 0, 0, 0, 0);
+                       pipe = para_open_audiod_pipe(args_info.stat_cmd_arg);
+                       break;
+               }
+       }
+}
diff --git a/sdl_gui.ggo b/sdl_gui.ggo
new file mode 100644 (file)
index 0000000..a318658
--- /dev/null
@@ -0,0 +1,9 @@
+# file sample1.ggo
+option "fullscreen" f "Use fullscreeen mode" flag off
+option "interactive" i "Activate interactive mode" flag off
+option "width" x "Specify screen width" int typestr="pixels" default="1024" no
+option "height" y "Specify screen height" int typestr="pixels" default="768" no
+option "config_file" c "(default='~/.paraslash/sdl_gui.conf')" string typestr="filename" no
+option "window-id" w "(currently ignored)" string typestr="filename" no
+option "stat_cmd" s "command to read server and audiod status data from" string typestr="command" default="para_audioc -t 100 stat" no
+option "pic_cmd" p "command to read pic from" string typestr="command" default="para_client pic" no
diff --git a/send.h b/send.h
new file mode 100644 (file)
index 0000000..2c6c727
--- /dev/null
+++ b/send.h
@@ -0,0 +1,86 @@
+/** \file send.h sender-related defines and structures */
+/** the sender subcommands */
+enum {SENDER_ADD, SENDER_DELETE, SENDER_ALLOW, SENDER_DENY, SENDER_ON, SENDER_OFF};
+
+/** the number of sender subcommands */
+#define NUM_SENDER_CMDS (SENDER_OFF + 1)
+
+/**
+ * describes one supported sender of para_server
+ *
+ * \sa http_send.c ortp_send.c
+ */
+struct sender {
+/** the name of the sender */
+       const char *name;
+/**
+ * the init function of this sender
+ *
+ * It must fill in all function pointers of \a s as well as the \a client_cmds
+ * array, see below. It should also do all neccessary preparations to init
+ * this sending facility, for example it could open a tcp port.
+ */
+       void (*init)(struct sender *s);
+/** \p SENDER_ON or \p SENDER_OFF */
+       int status;
+/**
+ * return the help text of this sender
+ *
+ * The result must be dynamically allocated and is freed by the caller.
+ */
+       char* (*help)(void);
+/**
+ * return current status info about this sender
+ *
+ * The result must be dynamically allocated and is freed by the caller.
+ */
+       char* (*info)(void);
+/**
+ * the send-hook
+ *
+ * It gets called whenever para_server is playing and the current
+ * audio format handler indicates that another chunk of data should
+ * be sent now. The two parameters \a current_chunk and \a chunks_sent
+ * only differ if the stream was repositioned by the \a ff or \a jmp
+ * command. Of course, \a buf is a pointer to the chunk of data which
+ * should be sent, and \a len is the length of this buffer.
+*/
+       void (*send)(struct audio_format *af, long unsigned current_chunk,
+               long unsigned chunks_sent, const char *buf, size_t len);
+/** add file descriptors to fd_sets
+ *
+ * The pre_select function of each supported sender is called just before
+ * para_server enters its main select loop. Each sender may add its own
+ * file descriptors to the \a rfds or the \a wfds set.
+ *
+ * If a file descriptor was added, \a max_fileno must be increased by
+ * this function, if neccessary.
+ *
+ * \sa select(2)
+*/
+       void (*pre_select)(struct audio_format *af, int *max_fileno, fd_set *rfds,
+               fd_set *wfds);
+/**
+ * handle the file descriptors which are ready for I/O
+ *
+ * If the pre_select hook added one ore more file descriptors to the read or write
+ * set, this is the hook to check the result and do any I/O on those descriptors
+ * which are ready for reading/writing.
+ */
+       void (*post_select)(struct audio_format *af, fd_set *rfds, fd_set *wfds);
+/**
+ * terminate all connected clients
+ *
+ * This is called e.g. if the stop command was executed. It should make the clients
+ * aware of the end-of-file condition.
+ */
+       void (*shutdown_clients)(void);
+/**
+ * array of function pointers for the sender subcommands
+ *
+ * Each sender may implement any subset of the sender commands by filling in
+ * the aprropriate function pointer in the array. A \p NULL pointer means this
+ * command is not implemented by this sender.
+ */
+       int (*client_cmds[NUM_SENDER_CMDS])(struct sender_command_data*);
+};
diff --git a/server.c b/server.c
new file mode 100644 (file)
index 0000000..71ee9eb
--- /dev/null
+++ b/server.c
@@ -0,0 +1,583 @@
+/*
+ * 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 server.c Paraslash's main server */
+
+
+/** \mainpage Paraslash API Reference
+ *
+ *  Good starting points for reading are probably \ref dbtool, \ref sender,
+ *  \ref receiver, \ref receiver_node, \ref filter, \ref filter_node.
+ *
+ */
+
+
+
+#include "server.cmdline.h"
+#include "db.h"
+#include "server.h"
+#include "afs.h"
+#include "config.h"
+#include "close_on_fork.h"
+#include <sys/mman.h> /* MAP_SHARED, PROT_READ, PROT_WRITE */
+#include "send.h"
+#include "error.h"
+#include "net.h"
+#include "daemon.h"
+#include "string.h"
+
+/** define the array of error lists needed by para_server */
+INIT_SERVER_ERRLISTS;
+
+/** shut down non-authorized connections after that many seconds */
+#define ALARM_TIMEOUT 10
+
+/* these are exported to afs/command/dbtool */
+struct misc_meta_data *mmd;
+/** the configuration of para_server
+ *
+ * It also contains the options for all database tools and all supported
+ * senders.
+*/
+struct gengetopt_args_info conf;
+char *user_list = NULL;
+extern void http_send_init(struct sender *);
+extern void ortp_send_init(struct sender *);
+extern struct audio_format afl[];
+
+/** the list of supported database tools */
+struct dbtool dblist[] = {
+       {
+               .name = "dopey",
+               .init = dopey_dbtool_init,
+               .update_audio_file = NULL,
+       },
+#ifdef HAVE_MYSQL
+       {
+               .name = "mysql",
+               .init = mysql_dbtool_init,
+               .update_audio_file = NULL,
+       },
+#endif
+       {
+               .name = NULL,
+       }
+};
+
+/** the list of supported senders */
+struct sender senders[] = {
+       {
+               .name = "http",
+               .init = http_send_init,
+       },
+#ifdef HAVE_ORTP
+       {
+               .name = "ortp",
+               .init = ortp_send_init,
+       },
+#endif
+       {
+               .name = NULL,
+       }
+};
+
+
+/* global variables for server-internal use */
+static FILE *logfile;
+static int mmd_semid;
+static int signal_pipe;
+
+/**
+ * para_server's log function
+ *
+ * \param ll the log level
+ * \param fmt the format string describing the log message
+ */
+void para_log(int ll, char* fmt,...)
+{
+       va_list argp;
+       FILE *outfd;
+       struct tm *tm;
+       time_t t1;
+       char str[MAXLINE] = "";
+       pid_t mypid;
+
+       if (ll < conf.loglevel_arg)
+               return;
+       if (!logfile) {
+               if (ll < WARNING)
+                       outfd = stdout;
+               else
+                       outfd = stderr;
+       } else
+               outfd = logfile;
+       if (conf.daemon_given && !logfile)
+               return;
+       time(&t1);
+       tm = localtime(&t1);
+       strftime(str, MAXLINE, "%b %d %H:%M:%S", tm);
+       fprintf(outfd, "%s ", str);
+       if (conf.loglevel_arg <= INFO)
+               fprintf(outfd, "%i: ", ll);
+       mypid = getpid();
+       if (conf.loglevel_arg <= INFO)
+               fprintf(outfd, "(%d) ", mypid);
+       va_start(argp, fmt);
+       vfprintf(outfd, fmt, argp);
+       va_end(argp);
+}
+
+
+/*
+ * setup shared memory area and get semaphore for locking
+ */
+static void shm_init(void)
+{
+       int fd;
+       caddr_t area;
+
+       if ((fd = open("/dev/zero", O_RDWR)) < 0) {
+               PARA_EMERG_LOG("%s", "failed to open /dev/zero\n");
+               exit(EXIT_FAILURE);
+       }
+       if ((area = mmap(0, sizeof(struct misc_meta_data),
+                       PROT_READ | PROT_WRITE,
+                       MAP_SHARED, fd, 0)) == (caddr_t) - 1) {
+               PARA_EMERG_LOG("%s", "mmap error\n");
+               exit(EXIT_FAILURE);
+       }
+       close(fd); /* we dont need /dev/zero anymore */
+       mmd = (struct misc_meta_data *)area;
+
+       mmd->dbt_num = 0;
+       mmd->num_played = 0;
+       mmd->num_commands = 0;
+       mmd->events = 0;
+       mmd->num_connects = 0;
+       mmd->active_connections = 0;
+       strcpy(mmd->filename, "(none)");
+       mmd->audio_format = -1;
+       mmd->afs_status_flags = AFS_NEXT;
+       mmd->new_afs_status_flags = AFS_NEXT;
+       mmd->sender_cmd_data.cmd_num = -1;
+
+       mmd_semid = semget(42, 1, IPC_CREAT | 0666);
+       if (mmd_semid == -1) {
+               PARA_EMERG_LOG("%s", "semget failed\n");
+               exit(EXIT_FAILURE);
+       }
+}
+
+static void do_semop(struct sembuf *sops, int num)
+{
+       if (semop(mmd_semid, sops, num) >= 0)
+               return;
+       PARA_WARNING_LOG("semop failed (%s), retrying\n", strerror(errno));
+       while (semop(mmd_semid, sops, num) < 0)
+               ; /* nothing */
+}
+
+/**
+ * lock the shared memory area containing the mmd struct
+ *
+ * \sa semop(2), struct misc_meta_data
+ */
+void mmd_lock(void)
+{
+       struct sembuf sops[2] = {
+               {
+                       .sem_num = 0,
+                       .sem_op = 0,
+                       .sem_flg = SEM_UNDO
+               },
+               {
+                       .sem_num = 0,
+                       .sem_op = 1,
+                       .sem_flg = SEM_UNDO
+               }
+       };
+       do_semop(sops, 2);
+}
+
+/**
+ * unlock the shared memory area containing the mmd struct
+ *
+ * \sa semop(2), struct misc_meta_data
+ */
+void mmd_unlock(void)
+{
+       struct sembuf sops[1] = {
+               {
+                       .sem_num = 0,
+                       .sem_op = -1,
+                       .sem_flg = SEM_UNDO
+               },
+       };
+       do_semop(sops, 1);
+}
+
+static void parse_config(int override)
+{
+       char *home = para_homedir();
+       struct stat statbuf;
+       int ret;
+       char *cf;
+
+       if (conf.config_file_given)
+               cf = conf.config_file_arg;
+       else
+               cf = make_message("%s/.paraslash/server.conf", home);
+       free(user_list);
+       if (!conf.user_list_given)
+               user_list = make_message("%s/.paraslash/server.users", home);
+       else
+               user_list = para_strdup(conf.user_list_arg);
+       ret = stat(cf, &statbuf);
+       if (ret && conf.config_file_given) {
+               ret = -1;
+               PARA_EMERG_LOG("can not stat config file %s\n", cf);
+               goto out;
+       }
+       if (!ret) {
+               int tmp = conf.daemon_given;
+               cmdline_parser_configfile(cf, &conf, override, 0, 0);
+               conf.daemon_given = tmp;
+       }
+       /* logfile */
+       if (!conf.logfile_given && conf.daemon_given) {
+               ret = -1;
+               PARA_EMERG_LOG("%s", "daemon, but no log file\n");
+               goto out;
+       }
+       if (conf.logfile_given)
+               logfile = open_log(conf.logfile_arg);
+       ret = 1;
+out:
+       free(cf);
+       free(home);
+       if (ret > 0)
+               return;
+       free(user_list);
+       user_list = NULL;
+       exit(EXIT_FAILURE);
+}
+
+static void setup_signal_handling(void)
+{
+       int ret = 0;
+
+       signal_pipe = para_signal_init();
+//     fcntl(signal_pipe, F_SETFL, O_NONBLOCK);
+       PARA_NOTICE_LOG("%s", "setting up signal handlers\n");
+       ret += para_install_sighandler(SIGINT);
+       ret += para_install_sighandler(SIGTERM);
+       ret += para_install_sighandler(SIGHUP);
+       ret += para_install_sighandler(SIGCHLD);
+       signal(SIGPIPE, SIG_IGN);
+       signal(SIGUSR1, SIG_IGN);
+       if (ret != 4) {
+               PARA_EMERG_LOG("%s", "could not install signal handlers\n");
+               exit(EXIT_FAILURE);
+       }
+}
+
+static void init_dbtool(void)
+{
+       int i;
+
+       mmd->dbt_change = -1; /* no change nec., set to new dbt num by com_cdt */
+       if (!dblist[1].name)
+               goto dopey;
+       if (conf.dbtool_given) {
+               for (i = 0; dblist[i].name; i++) {
+                       if (strcmp(dblist[i].name, conf.dbtool_arg))
+                               continue;
+                       PARA_NOTICE_LOG("initializing %s database tool\n",
+                               dblist[i].name);
+                       if (dblist[i].init(&dblist[i]) < 0) {
+                               PARA_WARNING_LOG("init %s failed",
+                                       dblist[i].name);
+                               goto dopey;
+                       }
+                       mmd->dbt_num = i;
+                       return;
+               }
+               PARA_WARNING_LOG("%s", "no such dbtool, switching to dopey\n");
+               goto dopey;
+       }
+       /* use the first dbtool that works
+        * (assuming that dopey always works)
+        */
+       for (i = 1; dblist[i].name; i++) {
+               int ret = dblist[i].init(&dblist[i]);
+               if (ret >= 0) {
+                       PARA_INFO_LOG("initialized %s\n", dblist[i].name);
+                       mmd->dbt_num = i;
+                       return;
+               }
+               PARA_CRIT_LOG("%s init failed: %s\n", dblist[i].name,
+                       PARA_STRERROR(-ret));
+       }
+dopey:
+       mmd->dbt_num = 0;
+       dblist[0].init(&dblist[0]); /* always successful */
+}
+
+static unsigned init_network(void)
+{
+       int sockfd = init_tcp_socket(conf.port_arg);
+
+       if (sockfd < 0)
+               exit(EXIT_FAILURE);
+       return sockfd;
+}
+
+static void init_random_seed(void)
+{
+       int fd, ret = -1, len = sizeof(unsigned int);
+       unsigned int seed;
+
+       fd = open("/dev/random", O_RDONLY);
+       if (fd < 0)
+               goto out;
+       ret = -2;
+       if (read(fd, &seed, len) != len)
+               goto out;
+       srandom(seed);
+       ret = 1;
+out:
+       if (fd >= 0)
+               close(fd);
+       if (ret > 0)
+               return;
+       PARA_EMERG_LOG("can not seed pseudo random generator (ret = %d)\n",
+               ret);
+       exit(EXIT_FAILURE);
+}
+
+static unsigned do_inits(int argc, char **argv)
+{
+       /* connector's address information */
+       int sockfd;
+
+       init_random_seed();
+       /* parse command line options */
+       cmdline_parser(argc, argv, &conf);
+       para_drop_privileges(conf.user_arg);
+       /* parse config file, open log and set defaults */
+       parse_config(0);
+       log_welcome("para_server", conf.loglevel_arg);
+       shm_init(); /* init mmd struct */
+       server_uptime(UPTIME_SET); /* reset server uptime */
+       /* become daemon */
+       if (conf.daemon_given)
+               daemon_init();
+       init_dbtool();
+       PARA_NOTICE_LOG("%s", "initializing audio file sender\n");
+       /* audio file sender */
+       afs_init();
+       mmd->server_pid = getpid();
+       setup_signal_handling();
+       mmd_lock();
+       /* init network socket */
+       PARA_NOTICE_LOG("%s", "initializing tcp command socket\n");
+       sockfd = init_network();
+       if (conf.autoplay_given) {
+               mmd->afs_status_flags |= AFS_PLAYING;
+               mmd->new_afs_status_flags |= AFS_PLAYING;
+       }
+       PARA_NOTICE_LOG("%s", "init complete\n");
+       return sockfd;
+}
+
+static void handle_dbt_change(void)
+{
+       int ret, old = mmd->dbt_num, new = mmd->dbt_change;
+
+       dblist[old].shutdown();
+       ret = dblist[new].init(&dblist[new]);
+       mmd->dbt_change = -1; /* reset */
+       if (ret >= 0) {
+               mmd->dbt_num = new;
+               return;
+       }
+       /* init failed */
+       PARA_ERROR_LOG("%s -- switching to dopey\n", PARA_STRERROR(-ret));
+       dblist[0].init(&dblist[0]);
+       mmd->dbt_num = 0;
+}
+
+/*
+ * called when server gets SIGHUP or when client invokes hup command.
+ */
+static void handle_sighup(void)
+{
+       PARA_NOTICE_LOG("%s", "SIGHUP\n");
+       close_log(logfile); /* gets reopened if necessary by parse_config */
+       logfile = NULL;
+       parse_config(1); /* reopens log */
+       mmd->dbt_change = mmd->dbt_num; /* do not change dbtool */
+       handle_dbt_change(); /* force reloading dbtool */
+}
+
+static void status_refresh(void)
+{
+       static int prev_uptime = -1, prev_events = -1;
+       int uptime = server_uptime(UPTIME_GET), ret = 1;
+
+       if (prev_events != mmd->events)
+               goto out;
+       if (mmd->new_afs_status_flags != mmd->afs_status_flags)
+               goto out;
+       if (uptime / 60 != prev_uptime / 60)
+               goto out;
+       ret = 0;
+out:
+       prev_uptime = uptime;
+       prev_events = mmd->events;
+       mmd->afs_status_flags = mmd->new_afs_status_flags;
+       if (ret) {
+               PARA_DEBUG_LOG("%d events, forcing status update, af = %d\n",
+                       mmd->events, mmd->audio_format);
+               killpg(0, SIGUSR1);
+       }
+}
+
+/*
+ * MAIN
+ */
+int main(int argc, char *argv[])
+{
+       /* listen on sock_fd, new connection on new_fd */
+       int sockfd, new_fd;
+       struct sockaddr_in their_addr;
+       int err, i, max_fileno, ret;
+       pid_t chld_pid;
+       fd_set rfds, wfds;
+       struct timeval *timeout;
+
+       valid_fd_012();
+       sockfd = do_inits(argc, argv);
+repeat:
+       /* check socket and signal pipe in any case */
+       FD_ZERO(&rfds);
+       FD_ZERO(&wfds);
+       FD_SET(sockfd, &rfds);
+       max_fileno = sockfd;
+       FD_SET(signal_pipe, &rfds);
+       max_fileno = MAX(max_fileno, signal_pipe);
+
+       timeout = afs_preselect();
+       status_refresh();
+       for (i = 0; senders[i].name; i++) {
+               if (senders[i].status != SENDER_ON)
+                       continue;
+               if (!senders[i].pre_select)
+                       continue;
+               senders[i].pre_select(mmd->audio_format >= 0?
+                       &afl[mmd->audio_format] : NULL,
+                       &max_fileno,
+                       &rfds, &wfds);
+       }
+       mmd_unlock();
+//     PARA_DEBUG_LOG("%s: select (max = %i)\n", __func__, max_fileno);
+       ret = select(max_fileno + 1, &rfds, &wfds, NULL, timeout);
+       err = errno;
+       //PARA_DEBUG_LOG("%s: select returned  %i\n", __func__, ret);
+       mmd_lock();
+       if (mmd->dbt_change >= 0)
+               handle_dbt_change();
+       if (ret < 0 && err == EINTR)
+               goto repeat;
+       if (ret < 0) {
+               PARA_CRIT_LOG("select error (%s)\n", strerror(err));
+               goto repeat;
+       }
+       for (i = 0; senders[i].name; i++) {
+               if (senders[i].status != SENDER_ON)
+                       continue;
+               if (!senders[i].post_select)
+                       continue;
+               senders[i].post_select(mmd->audio_format >= 0?
+                       &afl[mmd->audio_format] : NULL,
+                       &rfds, &wfds);
+       }
+       if (!ret) {
+               afs_send_chunk();
+               status_refresh();
+       }
+       if (FD_ISSET(signal_pipe, &rfds)) {
+               int sig;
+               sig = para_next_signal();
+               switch (sig) {
+               case SIGHUP:
+                       handle_sighup();
+                       break;
+               case SIGCHLD:
+                       para_reap_children();
+                       break;
+               /* die on sigint/sigterm. Kill all children too. */
+               case SIGINT:
+               case SIGTERM:
+                       PARA_EMERG_LOG("terminating on signal %d\n", sig);
+                       kill(0, SIGTERM);
+                       exit(EXIT_FAILURE);
+               }
+       }
+       if (mmd->sender_cmd_data.cmd_num >= 0) {
+               int num = mmd->sender_cmd_data.cmd_num,
+                       s = mmd->sender_cmd_data.sender_num;
+
+               if (senders[s].client_cmds[num])
+                       senders[s].client_cmds[num](&mmd->sender_cmd_data);
+               mmd->sender_cmd_data.cmd_num = -1;
+       }
+       if (!FD_ISSET(sockfd, &rfds))
+               goto repeat;
+
+       new_fd = para_accept(sockfd, &their_addr, sizeof(struct sockaddr_in));
+       if (new_fd < 0)
+               goto repeat;
+       PARA_INFO_LOG("got connection from %s, forking\n",
+               inet_ntoa(their_addr.sin_addr));
+       mmd->num_connects++;
+       mmd->active_connections++;
+       random();
+       chld_pid = fork();
+       if (chld_pid < 0) {
+               PARA_CRIT_LOG("%s", "fork failed\n");
+               goto repeat;
+       }
+       if (chld_pid) {
+               close(new_fd);
+               /* parent keeps accepting connections */
+               goto repeat;
+       }
+       alarm(ALARM_TIMEOUT);
+       close_listed_fds();
+       close(sockfd); /* child doesn't need the listener */
+       /*
+        * put info on who we are serving into argv[0] to make
+        * client ip visible in top/ps
+        */
+       for (i = argc - 1; i >= 0; i--)
+               memset(argv[i], 0, strlen(argv[i]));
+       sprintf(argv[0], "para_server (serving %s)",
+               inet_ntoa(their_addr.sin_addr));
+       return handle_connect(new_fd, &their_addr);
+}
diff --git a/server.ggo b/server.ggo
new file mode 100644 (file)
index 0000000..52dc90b
--- /dev/null
@@ -0,0 +1,43 @@
+section "General options"
+option "loglevel" l "set loglevel (0-6)" int typestr="level" default="4" no
+option "port" p "port to listen on" int typestr="portnumber" default="2990" no
+option "daemon" d "run as background daemon" flag off
+option "user" u "run as user 'name'. para_server does not need any special privileges. If started as root (EUID == 0) this option must be given at the command line (not in the configuration file) so that para_server can drop the root privileges right after parsing the command line options, but before parsing the configuration file. In this case, real/effective/saved UID are all set to the UID of 'username'. As the configuration file is read afterwards, those options that have a default value depending on the UID (e.g. the home directory for the configuration file) are computed by using the uid of 'username'. This option has no effect if para_server ist started as a non-root user (i.e.  EUID != 0)" string typestr="name" no
+
+section "Configuration files"
+option "logfile" L "(default=stdout/stderr)" string typestr="filename" no
+option "config_file" c "(default='~/.paraslash/server.conf'" string typestr="filename" no
+option "user_list" - "(default='~/.paraslash/server.users')" string typestr="filename" no
+
+section "Options concerning the audio file sender"
+option "autoplay" a "start playing on startup" flag off
+option "announce_time" A "Delay betweeen announcing the stream and sending data" int typestr="milliseconds" default="300" no
+option "dbtool" D "(default=first available that works)" string typestr="name_of_dbtool" no
+
+section "Mysql database tool options"
+option "mysql_host" - "mysql server" string default="localhost" no
+option "mysql_port" - "where mysql is listening" int default="3306" no
+option "mysql_user" - "default value: username from /etc/passwd" string no
+option "mysql_passwd" - "(required)" string no
+option "mysql_database" - "name of mysql database" string default="paraslash" no
+option "mysql_audio_file_dir"  - "dir to search for audio files (required)" string no
+option "mysql_default_score" - "scoring rule to use if stream definition does not contain explicit score definition" string default="(LASTPLAYED() / 1440 - 1000 / (LASTPLAYED() + 1) - sqrt(NUMPLAYED()))" no
+
+
+
+section "Dopey database tool options"
+option "dopey_dir" - "dir to search for files to be streamed" string default="/home/music" no
+
+section "Http sender options"
+option "http_port" - "tcp port for http streaming" int typestr="portnumber" default="8000" no
+option "http_default_deny" - "deny connections from hosts which are not explicitly allowed" flag off
+option "http_access" - "Add given host/network to access control list (whitelist if http_default_deny was given, blacklist otherwise) before opening the tcp port. This option can be given multiple times. Example: '192.168.0.0/24' whitelists/blacklists the 256 hosts 192.168.0.x" string typestr="a.b.c.d/n" no multiple
+option "http_no_autostart" - "do not open tcp port on server startup" flag off
+option "http_max_clients" - "maximal simultaneous connections, non-positive value means unlimited" int typestr="number" default="-1" no
+
+section "Ortp sender options"
+option "ortp_target" - "Add given host/port to the list of targets. This option can be given multiple times. Example: '224.0.1.38:1500' instructs the ortp sender to send to udp port 1500 on host 224.0.1.38 (unassigned ip in the Local Network Control Block 224.0.0/24). This is useful for LAN-streaming." string typestr="a.b.c.d:p" no multiple
+option "ortp_no_autostart" - "do not start to send automatically" flag off
+option "ortp_default_port" - "default udp port if not specified" int typestr="portnumber" default="1500" no
+option "ortp_header_interval" H "time between extra header sends" int typestr="milliseconds" default="2000" no
+
diff --git a/server.h b/server.h
new file mode 100644 (file)
index 0000000..b01c3f6
--- /dev/null
+++ b/server.h
@@ -0,0 +1,155 @@
+/*
+ * 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 server.h common server data structures */
+
+#include "para.h"
+
+
+/** size of the dbinfo and audio_file info strings of struct misc_meta_data */
+#define MMD_INFO_SIZE 16384
+
+/**
+ * permission flags that can be set individually for any server command
+ *
+ * - DB_READ: command reads from the database
+ * - DB_WRITE: command changes the contents of the database
+ * - AFS_READ: command reads information about the current audio stream
+ * - AFS_WRITE: command changes the current audio stream
+ */
+enum {DB_READ = 1, DB_WRITE = 2, AFS_READ = 4, AFS_WRITE = 8};
+
+/**
+ * data needed to authenticate the user
+ */
+struct user{
+/** the username */
+       char name[MAXLINE];
+/** full path to the public RSA key */
+       char pubkey_file[_POSIX_PATH_MAX];
+/** the privileges of this user */
+       unsigned int perms;
+};
+
+/**
+ * defines one command of para_server
+ */
+struct server_command {
+/** the name of the command */
+       char *name;
+/** pointer to the function that handles the command */
+       int (*handler)(int, int, char **);
+/** the privileges a user must have to execute this command */
+       unsigned int perms;
+/** one-line description of the command */
+       char *description;
+/** summary of the command line options */
+       char *synopsis;
+/** the long help text */
+       char *help;
+};
+
+/** holds the arguments for the para_server's sender command */
+struct sender_command_data{
+/** greater than 0 indicates that a sender cmd is already queued */
+       int cmd_num;
+/** the number of the sender in question */
+       int sender_num;
+/** used for the allow/deny/add/remove subcommands */
+       struct in_addr addr;
+/** used for allow/deny */
+       int netmask;
+/** the portnumber for add/remove */
+       int port;
+};
+
+/**
+ * used for parent-child communication
+ *
+ * There's only one struct of this type which lives in shared memory
+ * for communication between the server instances. Access to this
+ * area is serialized via mmd_lock() and mmd_unlock(). There are two
+ * reasons for a variable to be included here:
+ *
+ *     - At least one command (i.e. child of the server) must be able to
+ *     change its value.
+ *
+ * or
+ *
+ *     - The contents are listed in the stat command and have to be up to
+ *     date.
+ */
+struct misc_meta_data{
+/** the size of the current audio file in bytes */
+       long unsigned int size;
+/** the full path of the current audio file */
+       char filename[_POSIX_PATH_MAX];
+/** the last modification file of the current audio file */
+       time_t mtime;
+/* the number of the current audio format */
+       int audio_format;
+/** the "old" status flags -- commands may only read them */
+       unsigned int afs_status_flags;
+/** the new status flags -- commands may set them **/
+       unsigned int new_afs_status_flags;
+/** the number of data chunks sent for the current audio file */
+       long unsigned chunks_sent;
+/** the number of chunks this audio file contains */
+       long unsigned chunks_total;
+/** set by the jmp/ff commands to the new position in chunks */
+       long unsigned repos_request;
+/** the number of the chunk currently sent out*/
+       long unsigned current_chunk;
+/** the milliseconds that have been skipped of the current audio file */
+       long offset;
+/** the length of the audio file in seconds */
+       int seconds_total;
+/** the time para_server started to stream */
+       struct timeval stream_start;
+/** a string that gets filled in by the audio format handler */
+       char audio_file_info[MMD_INFO_SIZE];
+/** the event counter
+ *
+ * commands may increase this to force a status update to be sent to all
+ * connected clients
+*/
+       unsigned int events;
+/** the number of audio files already sent */
+       unsigned int num_played;
+/** the number of executed commands */
+       unsigned int num_commands;
+/** the number of connections para_server received so far */
+       unsigned int num_connects;
+/** the number of connections currently active */
+       unsigned int active_connections;
+/** the process id of para_server */
+       pid_t server_pid;
+/** a string that gets filled in by the current database tool */
+       char dbinfo[MMD_INFO_SIZE];
+/** the number if the current database tool */
+       int dbt_num;
+/** commands set this to non-zero to request a database tool change */
+       int dbt_change;
+/* used by the sender command */
+       struct sender_command_data sender_cmd_data;
+};
+
+
+int handle_connect(int fd, struct sockaddr_in *addr);
+void mmd_unlock(void);
+void mmd_lock(void);
diff --git a/signal.c b/signal.c
new file mode 100644 (file)
index 0000000..82444cc
--- /dev/null
+++ b/signal.c
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2004-2006 Andre Noll <maan@systemlinux.org>
+ *
+ *     This program is free software; you can redistribute it and/or modify
+ *     it under the terms of the GNU General Public License as published by
+ *     the Free Software Foundation; either version 2 of the License, or
+ *     (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU General Public License for more details.
+ *
+ *     You should have received a copy of the GNU General Public License
+ *     along with this program; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ */
+/** \file signal.c signal handling functions */
+
+#include "para.h"
+#include "error.h"
+static int signal_pipe[2];
+
+/**
+ * initialize the paraslash signal subsystem
+ *
+ * This function creates a pipe, the signal pipe, to deliver pending signals to
+ * the application. It should be called during the application's startup part,
+ * followed by subsequent calls to para_install_sighandler() for each signal
+ * that should be caught.
+ *
+ * para_signal_init() installs a generic signal handler which is used for all
+ * signals simultaneously. When a signal arrives, this generic signal handler
+ * writes the corresponding signal number to the signal pipe so that the
+ * application can test for pending signals simply by checking the signal pipe
+ * for reading, e.g. by using the select(2) system call.
+ *
+ * \return This function either succeeds or calls exit(2) to terminate
+ * the current process. On success, the file descriptor of the signal pipe is
+ * returned.
+ */
+int para_signal_init(void)
+{
+       if (!pipe(signal_pipe))
+               return signal_pipe[0];
+       PARA_EMERG_LOG("%s", "pipe error: Can not setup signal pipe");
+       exit(EXIT_FAILURE);
+}
+
+/*
+ * just write one integer to signal pipe
+ */
+static void generic_signal_handler(int s)
+{
+       write(signal_pipe[1], &s, sizeof(int));
+       //fprintf(stderr, "got sig %i, write returned %d\n", s, ret);
+}
+
+/**
+ * reap one child
+ *
+ * call waitpid() and print a log message containing the pid
+ * and the cause of the child's death.
+ *
+ * \return Like \p waitpid(), this function returns the process ID of the
+ * terminated child; on error, \p -E_WAITPID is returned.
+ * \sa waitpid(2)
+ */
+pid_t para_reap_child(void)
+{
+       int status;
+       pid_t pid = waitpid(-1, &status, WNOHANG);
+
+       if (pid <= 0) {
+               if (pid < 0)
+                       pid = -E_WAITPID;
+               return 0;
+       }
+       if (WIFEXITED(status))
+               PARA_DEBUG_LOG("child %i exited. Exit status: %i\n", pid,
+                       WEXITSTATUS(status));
+       else if (WIFSIGNALED(status))
+               PARA_DEBUG_LOG("child %i was killed by signal %i\n", pid,
+                       WTERMSIG(status));
+       else
+               PARA_WARNING_LOG("child %i terminated abormally\n", pid);
+       return pid;
+}
+
+/**
+ * paraslash's zombie killer
+ *
+ * It just calls \p para_reap_child() until there are no more children left to
+ * reap.
+ */
+void para_reap_children(void)
+{
+       while (para_reap_child() > 0)
+               ; /* nothing */
+}
+
+/**
+ * wrapper around signal(2)
+ * \param sig the number of the signal to catch
+ *
+ * This installs the generic signal handler for the given signal.
+ * \return This function returns 1 on success and \p -E_SIGNAL_SIG_ERR on errors.
+ * \sa signal(2)
+ */
+int para_install_sighandler(int sig)
+{
+       PARA_DEBUG_LOG("catching signal %d\n", sig);
+       return signal(sig, &generic_signal_handler) == SIG_ERR?  -E_SIGNAL_SIG_ERR : 1;
+}
+
+/**
+ * return number of next pending signal
+ *
+ * This should be called if the fd for the signal pipe is ready for reading.
+ *
+ * \return On success, the number of the received signal is returned. \p
+ * -E_SIGNAL_READ is returned if a read error occured while reading the signal
+ * pipe.  If the read was interrupted by another signal the function returns 0.
+ */
+int para_next_signal(void)
+{
+       int s;
+       ssize_t r;
+
+       if ((r = read(signal_pipe[0], &s, sizeof(s)) == sizeof(s)) > 0) {
+               PARA_DEBUG_LOG("next signal: %d\n", s);
+               return s;
+       }
+       return r < 0 && (errno != EAGAIN)? 0 : -E_SIGNAL_READ;
+}
diff --git a/skencil/overview.sk b/skencil/overview.sk
new file mode 100644 (file)
index 0000000..f4427df
--- /dev/null
@@ -0,0 +1,445 @@
+##Sketch 1 2
+document()
+layout('A4',0)
+layer('Layer 1',1,1,0,0,(0,0,0))
+G()
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('SQL query',(3.06152e-16,1,-1,3.06152e-16,73.5128,678.229))
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(76.5508,667.712,0)
+bs(76.5508,750.57,0)
+G_()
+G()
+lw(1)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(205.995,666.16,0)
+bs(205.995,744.222,0)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('open file',(1.19433e-15,1,-1,1.19433e-15,202.957,680.502))
+G_()
+G()
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('mp3 data',(-1.83691e-16,-1,1,-1.83691e-16,241.524,730.112))
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(238.486,739.793,0)
+bs(238.486,668.714,0)
+G_()
+G()
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('fork & exec',(1.19433e-15,1,-1,1.19433e-15,215.967,398.919))
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(219.005,388.674,0)
+bs(219.005,476.027,0)
+G_()
+G()
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(238.331,474.579,0)
+bs(238.331,392.999,0)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('server status',(-1.83691e-16,-1,1,-1.83691e-16,241.369,468.586))
+G_()
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(145.824,181.165,0)
+bs(228.568,181.165,0)
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(228.568,161.165,0)
+bs(145.824,161.165,0)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('connect local socket',(1.19433e-15,1,-1,1.19433e-15,272.988,207.639))
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(307.4,339.815,0)
+bs(307.4,200,0)
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(280,200,0)
+bs(280,339.815,0)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('server & audiod status',(-1.83691e-16,-1,1,-1.83691e-16,310.438,332.705))
+G()
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(99.2303,144.289,0)
+bs(99.2303,73.1918,0)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('s&a status',(-1.83691e-16,-1,1,-1.83691e-16,102.268,137.713))
+G_()
+G()
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(61.1648,150.482,0)
+bs(61.1647,71.2323,0)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('cmd output',(-1.83691e-16,-1,1,-1.83691e-16,64.2028,142.553))
+G_()
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(498.256,150.683,0)
+bs(498.256,72.1559,0)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('pcm data',(-1.83691e-16,-1,1,-1.83691e-16,481.11,134.249))
+lw(0.992126)
+ld((0.10000000000000001, 1.0))
+b()
+bs(24.7094,792.443,0)
+bs(310.724,792.443,0)
+bs(310.421,682.725,0)
+bs(561.98,682.585,0)
+bs(561.98,609.679,0)
+bs(24.7094,609.679,0)
+bs(24.7094,792.443,0)
+bC()
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Italic')
+txt('server',(523.826,666.279))
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Italic')
+txt('client',(334.671,487.931))
+G()
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(105.06,750.094,0)
+bs(105.06,667.236,0)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('mysql result',(-1.83691e-16,-1,1,-1.83691e-16,108.098,743.084))
+G_()
+G()
+lw(1)
+r(78.7206,0,0,-28.2587,185.03,786.713)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(24)
+txt('storage',(189.734,766.992))
+G_()
+G()
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(24)
+txt('mysqld',(35.977,766.992))
+lw(1)
+r(75.6929,0,0,-28.2587,33.4706,786.713)
+G_()
+G()
+lw(1)
+r(119.09,0,0,-28.2587,39.0214,375.322)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(24)
+txt('para_client',(45.2504,355.601))
+G_()
+lw(1)
+r(119.09,0,0,-28.2587,180.229,509.717)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(24)
+txt('para_client',(186.458,489.996))
+G()
+lw(1)
+r(143.312,0,0,-28.2587,413.631,59.6823)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(24)
+txt('sound_device',(419.299,39.9609))
+G_()
+G()
+lw(1)
+r(94.8684,0,0,-28.2587,34.9844,58.6731)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(24)
+txt('terminal',(42.4226,38.9517))
+G_()
+G()
+lw(1)
+r(131.201,0,0,-28.2587,237.937,189.4)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(24)
+txt('para_audioc',(244.894,169.678))
+G_()
+G()
+lw(1)
+r(94.8684,0,0,-28.2587,34.9844,189.4)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(24)
+txt('para_gui',(40.4306,169.678))
+G_()
+lw(1)
+r(114.044,0,0,-28.2587,441.89,188.865)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(24)
+txt('para_play',(448.5,169.679))
+G()
+G()
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(216.8,522.254,0)
+bs(216.8,603.141,0)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('connect tcp',(1.19433e-15,1,-1,1.19433e-15,213.762,530.624))
+G_()
+G()
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('server status',(-1.83691e-16,-1,1,-1.83691e-16,249.561,597.495))
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(246.523,603.141,0)
+bs(246.523,522.254,0)
+G_()
+G_()
+fp((0.9,0.9,0.9))
+lw(1)
+r(177.037,0,0,-89.6831,384.699,788.574)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Bold')
+Fs(36)
+txt('paraslash',(399.201,755.055))
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Bold')
+Fs(36)
+txt('0.2.9',(437.217,715.454))
+G()
+lw(1)
+r(517.739,0,0,-28.2587,33.4706,653.823)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(24)
+txt('para_server',(236.372,634.102))
+G_()
+G()
+lw(1)
+r(360.298,0,0,-28.2587,199.586,375.322)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(24)
+txt('para_audiod',(320.419,355.601))
+G_()
+G()
+G()
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(514.857,324.515,0)
+bs(514.857,209.912,0)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('wav data',(-1.83691e-16,-1,1,-1.83691e-16,517.895,292.288))
+G_()
+G()
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('fork & exec',(-1.83691e-16,-1,1,-1.83691e-16,484.591,300.646))
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(481.553,324.515,0)
+bs(481.553,209.912,0)
+G_()
+G_()
+G()
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('fork & exec',(153.764,182.009))
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('s&a status',(158.223,162.009))
+G_()
+G()
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('fork & exec',(1.19433e-15,1,-1,1.19433e-15,70.3615,232.519))
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(73.3994,201.263,0)
+bs(73.3995,330.639,0)
+G_()
+G()
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(103.54,330.639,0)
+bs(103.539,201.263,0)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('cmd output',(-1.83691e-16,-1,1,-1.83691e-16,106.577,297.647))
+G_()
+G()
+lw(1)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(505.839,595.876,0)
+bs(505.839,400.592,0)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('mp3 data ',(-1.83691e-16,-1,1,-1.83691e-16,508.877,525.842))
+G_()
+G()
+G()
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('connect tcp, send cmd',(2.57235e-15,1,-1,2.57235e-15,64.0626,431.071))
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(67.1006,390.148,0)
+bs(67.1006,596.411,0)
+G_()
+G()
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(101.501,596.411,0)
+bs(101.501,390.148,0)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('cmd output',(-1.83691e-16,-1,1,-1.83691e-16,104.539,524.976))
+G_()
+G_()
+lw(0.992126)
+ld((0.10000000000000001, 1.0))
+b()
+bs(29.1803,383.603,0)
+bs(164.433,383.603,0)
+bs(164.433,513.811,0)
+bs(396.545,513.811,0)
+bs(396.545,383.568,0)
+bs(570.132,383.568,0)
+bs(570.132,26.3198,0)
+bs(27.1619,26.3198,0)
+bs(27.1619,382.595,0)
+guidelayer('Guide Lines',1,0,0,1,(0,0,1))
+guide(-307.905,0)
+grid((0,0,20,20),0,(0,0,1),'Grid')
diff --git a/slider.c b/slider.c
new file mode 100644 (file)
index 0000000..558f55b
--- /dev/null
+++ b/slider.c
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2004-2006 Andre Noll <maan@systemlinux.org>
+ *
+ *     This program is free software; you can redistribute it and/or modify
+ *     it under the terms of the GNU General Public License as published by
+ *     the Free Software Foundation; either version 2 of the License, or
+ *     (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU General Public License for more details.
+ *
+ *     You should have received a copy of the GNU General Public License
+ *     along with this program; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ */
+
+/** \file slider.c libzmw-based stream creator for paraslash */
+
+#include "para.h"
+#include "zmw/zmw.h"
+#include "math.h"
+#include "string.h"
+
+#define WIDTH 300
+#define SLIDER_RATIO 6
+#define VAL_2_SL_VAL(v) (v) * (SLIDER_RATIO - 1.0) / SLIDER_RATIO
+#define SL_VAL_2_VAL(v) (v) * SLIDER_RATIO / (SLIDER_RATIO - 1.0)
+#define VAL_2_SCORE(v) (v) > 0.5? 1 / (1.03 - (v)) / (1.03 - (v)) :\
+        - 1 / ((v) + 0.03) / ((v) + 0.03)
+
+#define RGB(R,G,B) (((R)<<12) + ((G)<<6) + (B))
+
+#define EPSILON 0.001
+#define LASTPLAYED_FORMULA(v) \
+       1 / (pow((v), 10) + EPSILON * EPSILON) - 1 / (1 + EPSILON) + EPSILON
+
+#define NUMPLAYED_FORMULA(v)\
+       10 * (v) + (v) / (1 - (v) * (1 - EPSILON))
+
+
+static int argc;
+static char **argv;
+char *streamname = NULL;
+static Zmw_Float_0_1 *slider_vals, lastplayed_val, numplayed_val;
+
+#if 0
+static void boxed_text(const char *text, int i)
+{
+  ZMW(zmw_decorator(Zmw_Decorator_Border_Embossed))
+    {
+      if (i == 2)
+        {
+          zmw_horizontal_expand(Zmw_False) ;
+          zmw_vertical_expand(Zmw_False) ;
+        }
+      zmw_label(text) ;
+    }
+}
+#endif
+
+void para_log(int ll, char* fmt,...) /* no logging */
+{
+}
+
+static void rst(void)
+{
+       int i;
+       for (i = 1; argv[i]; i++)
+               slider_vals[i] = VAL_2_SL_VAL(.5);
+       if (streamname)
+               free(streamname);
+       streamname = para_strdup("slider");
+       //printf("rst: \n");
+}
+
+static char *get_value_filename(void)
+{
+       char *ret, *home =para_homedir();
+       ret = make_message("%s/.paraslash/slide.values", home);
+       free(home);
+       return ret;
+}
+
+static void load(void)
+{
+       char *tmp;
+       char att[999];
+       FILE *f;
+       int i;
+       Zmw_Float_0_1 val;
+
+       tmp = get_value_filename();
+       f  = fopen(tmp, "r");
+       free(tmp);
+       if (!f)
+               return;
+       while (fscanf(f, "%900s %g", att, &val) == 2) {
+               if (!strcmp(att, "lastplayed")) {
+                       lastplayed_val = val;
+                       continue;
+               }
+               if (!strcmp(att, "numplayed")) {
+                       numplayed_val = val;
+                       continue;
+               }
+               for (i = 1; argv[i]; i++)
+                       if (!strcmp(argv[i], att)) {
+                               slider_vals[i] = val;
+                               break;
+                       }
+
+       }
+       fclose(f);
+}
+
+static void save(void)
+{
+       char *filename;
+       FILE *f;
+       int i;
+
+       filename = get_value_filename();
+       f  = fopen(filename, "w");
+       free(filename);
+       if (!f)
+               return;
+       fprintf(f, "lastplayed %g\n", lastplayed_val);
+       fprintf(f, "numplayed %g\n", numplayed_val);
+       for (i = 1; argv[i]; i++)
+               fprintf(f, "%s %g\n", argv[i], slider_vals[i]);
+       fclose(f);
+}
+
+
+static void print_score(FILE *fd)
+{
+       int i;
+
+       for (i = 1; argv[i]; i++) {
+               if (SL_VAL_2_VAL(slider_vals[i]) > .99)
+                       fprintf(fd, "deny: IS_N_SET(%s)\n", argv[i]);
+               if (slider_vals[i] < .01)
+                       fprintf(fd, "deny: IS_SET(%s)\n", argv[i]);
+       }
+       fprintf(fd, "score: 0 ");
+       for (i = 1; argv[i]; i++) {
+               if (slider_vals[i] < .01)
+                       continue;
+               fprintf(fd, "+ %i * IS_SET(%s) ",
+                       (int) (VAL_2_SCORE(SL_VAL_2_VAL(slider_vals[i]))),
+                       argv[i]
+               );
+       }
+       fprintf(fd, " + round((LASTPLAYED()/1440 - 1000 / (LASTPLAYED()/1440 + 1)) / %f"
+                       " - %f * NUMPLAYED(), 2)\n",
+               LASTPLAYED_FORMULA(SL_VAL_2_VAL(lastplayed_val)),
+               NUMPLAYED_FORMULA(SL_VAL_2_VAL(numplayed_val)));
+}
+
+static void stradd(void)
+{
+       FILE *pipe;
+       char *cmd = make_message("para_client stradd %s", streamname);
+
+       pipe = popen(cmd, "w");
+       free(cmd);
+       if (!pipe)
+               return;
+       print_score(pipe);
+       pclose(pipe);
+}
+
+static void b_save(void)
+{
+       stradd();
+       save();
+}
+
+static void go(void)
+{
+       stradd();
+       system("para_client cs slider");
+       system("para_client stop");
+       system("para_client play");
+}
+
+static void ok()
+{
+       stradd();
+       system("para_client cs slider");
+       system("para_client play");
+       save();
+       zmw_main_quit(EXIT_SUCCESS);
+}
+
+static void make_buttons(void)
+{
+       /* Add a frame around the box */
+       ZMW(zmw_decorator(Zmw_Decorator_Border_Embossed)) {
+               /* the buttons */
+               ZMW(zmw_hbox()) {
+                       zmw_button("save");
+                       if (zmw_activated())
+                               b_save();
+                       zmw_button("ok");
+                       if (zmw_activated())
+                               ok();
+                       zmw_button("go!");
+                       if (zmw_activated())
+                               go();
+                       zmw_button("rst");
+                       if (zmw_activated())
+                               rst();
+//                     zmw_x(WIDTH);
+                       zmw_button("abort");
+                       if (zmw_activated())
+                               zmw_main_quit(EXIT_FAILURE);
+               }
+       }
+}
+static void make_stream_input_field(void)
+{
+       static char *text1 = NULL;
+       static int cursor_pos = 1; // Must be static
+       static int text1_length ;
+
+       ZMW(zmw_vbox()) {
+               if ( text1 == NULL ) {
+                       // The initial value must be malloced
+                       text1 = strdup("slider") ;
+                       text1_length = strlen(text1) ;
+               }
+               ZMW(zmw_fixed()) {
+                       zmw_y(6);
+                       zmw_x(0);
+                       zmw_label("stream: ");
+                       zmw_color(Zmw_Color_Foreground, RGB(63, 63, 63));
+                       zmw_y(0);
+                       zmw_x(100);
+                       zmw_width(WIDTH);
+                       zmw_entry_with_cursor(&streamname, &cursor_pos);
+                       if (zmw_changed())
+                               text1_length = strlen(text1);
+               }
+       }
+}
+
+/* the sliders, one for each member in argv */
+static void make_sliders(void)
+{
+       int i;
+       char *txt;
+
+       zmw_height(ZMW_VALUE_UNDEFINED);
+//               zmw_horizontal_expand(Zmw_True);
+       ZMW(zmw_hbox()) {
+               ZMW(zmw_vbox()) {
+                       zmw_color(Zmw_Color_Foreground, RGB(0, 63, 63)); /* font */
+                       txt = make_message("lp: (%i%%)",
+                               (int)(.5 + 100 * SL_VAL_2_VAL(lastplayed_val)));
+                       zmw_label(txt);
+                       free(txt);
+                       txt = make_message("np: (%i%%)",
+                               (int)(.5 + 100 * SL_VAL_2_VAL(numplayed_val)));
+                       zmw_label(txt);
+                       free(txt);
+                       zmw_color(Zmw_Color_Foreground, RGB(63, 63, 0)); /* font */
+                       for (i = 1; argv[i]; i++) {
+                               txt = make_message("%s: (%i%%)", argv[i],
+                                       (int)(.5 + 100 * SL_VAL_2_VAL(slider_vals[i])));
+                               zmw_label(txt);
+                               free(txt);
+                       }
+               }
+               ZMW(zmw_vbox()) {
+                       zmw_width(200) ;
+                       zmw_hscrollbar(&lastplayed_val, 1.0 / SLIDER_RATIO);
+                       zmw_hscrollbar(&numplayed_val, 1.0 / SLIDER_RATIO);
+                       for (i = 1; argv[i]; i++) {
+                               zmw_hscrollbar(&slider_vals[i],
+                                       1.0 / SLIDER_RATIO);
+                       }
+               }
+       }
+}
+
+static void anchor(void)
+{
+
+       zmw_font_family("terminal");
+       zmw_font_size(14);
+       zmw_rgb(0.1, 0.1, 0.1);
+       zmw_color(Zmw_Color_Foreground, RGB(63, 63, 0)); /* font */
+       zmw_color(Zmw_Color_Background_Pushed, RGB(0, 10, 4)); /* slider bg */
+       zmw_color(Zmw_Color_Background_Poped, RGB(0, 42, 42)); /* button bg */
+       zmw_color(Zmw_Color_Border_Light, RGB(0, 7, 63)); /* slider/button border */
+
+       ZMW(zmw_window("para_slider")) {
+               ZMW(zmw_vbox()) {
+                       make_buttons();
+                       make_stream_input_field();
+                       zmw_color(Zmw_Color_Foreground, RGB(0, 63, 63)); /* font */
+//                     make_special_sliders();
+                       zmw_color(Zmw_Color_Foreground, RGB(63, 63, 0)); /* font */
+                       make_sliders();
+               }
+       }
+}
+
+static void get_atts(void)
+{
+       char buf[256];
+       FILE *p = popen("para_client laa", "r");
+       argv = para_malloc(255 * sizeof(char *)); /* FIXME */
+
+       argv[0] = para_strdup("all_attributes");
+       argc = 1;
+       if (!p)
+               goto err_out;
+       while (fgets(buf, 255, p)) {
+               chop(buf);
+               argv[argc] = para_strdup(buf);
+               argc++;
+       }
+       pclose(p);
+       argv[argc] = NULL;
+       argc--;
+       if (argc > 1)
+               return; /* success */
+err_out:
+       if (argc > 1)
+               for (argc = 1; argv[argc]; argc++)
+                       free(argv[argc]);
+       free(argv);
+       argc = 1;
+}
+
+int main(int the_argc, char *the_argv[])
+{
+       argc = the_argc;
+       argv = the_argv;
+       if (argc < 2) {
+               get_atts();
+               if (argc < 2)
+                       return -1;
+       }
+       slider_vals = para_malloc((argc + 1) * sizeof(int*));
+       rst();
+       load();
+//     printf("argc: %d\n", argc);
+//     for (i = 1; argv[i]; i++)
+//             printf("argv[%d]: %s\n", i, argv[i]);
+       zmw_init(&argc, &argv);
+       zmw_main(anchor);
+       return 0;
+}
diff --git a/stat.c b/stat.c
new file mode 100644 (file)
index 0000000..7550f2d
--- /dev/null
+++ b/stat.c
@@ -0,0 +1,284 @@
+/*
+ * 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 stat.c functions used for sending/receiving the status of para_server
+ *  and para_audiod
+ */
+#include "para.h"
+#include "close_on_fork.h"
+#include "list.h"
+#include "error.h"
+#include "string.h"
+
+/** the maximal number of simultaneous connections */
+#define MAX_STAT_CLIENTS 50
+
+/**
+ * The structure associated with a connected client that sent the 'stat' command
+*
+ *
+ * A status client is identified by its file descriptor.  para_audiod
+ * keeps a list of connected status clients.
+ */
+struct stat_client {
+/**
+ *
+ *
+ * the stat client's file descriptor
+ */
+int fd;
+/**
+ *
+ * its entry in the list of stat clients
+*/
+struct list_head node;
+};
+
+static struct list_head client_list;
+static int initialized;
+static int num_clients;
+
+/**
+ * the list of all status items sent by para_server/para_audiod
+ */
+const char *status_item_list[NUM_STAT_ITEMS] = {
+       [SI_STATUS_BAR] = "status_bar",
+       [SI_STATUS] = "status",
+       [SI_NUM_PLAYED] = "num_played",
+
+       [SI_MTIME] = "mtime",
+       [SI_LENGTH_MIN] = "length_min",
+       [SI_LENGTH_SEC] = "length_sec",
+
+       [SI_FILE_SIZE] = "file_size",
+       [SI_STATUS_FLAGS] = "status_flags",
+       [SI_FORMAT] = "format",
+
+       [SI_SCORE] = "score",
+       [SI_AUDIO_INFO1] = "audio_file_info1",
+       [SI_AUDIO_INFO2] = "audio_file_info2",
+
+       [SI_AUDIO_INFO3] = "audio_file_info3",
+       [SI_DBINFO1] = "dbinfo1",
+       [SI_DBINFO2] = "dbinfo2",
+
+       [SI_DBINFO3] = "dbinfo3",
+       [SI_DECODER_FLAGS] = "decoder_flags",
+       [SI_AUDIOD_STATUS] = "audiod_status",
+
+       [SI_PLAY_TIME] = "play_time",
+       [SI_UPTIME] = "uptime",
+       [SI_OFFSET] = "offset",
+
+       [SI_LENGTH] = "length",
+       [SI_STREAM_START] = "stream_start",
+       [SI_CURRENT_TIME] = "current_time",
+
+       [SI_AUDIOD_UPTIME] = "audiod_uptime",
+       [SI_DBTOOL] = "dbtool"
+};
+#define FOR_EACH_STAT_ITEM(i) for (i = 0; i < NUM_STAT_ITEMS; i++)
+
+static void dump_stat_client_list(void)
+{
+       struct stat_client *sc;
+
+       if (!initialized)
+               return;
+       list_for_each_entry(sc, &client_list, node)
+               PARA_INFO_LOG("stat client on fd %d\n", sc->fd);
+}
+/**
+ * add a status client to the list
+ *
+ * \return Positive value on success, or -E_TOO_MANY_CLIENTS if
+ * the number of connected clients exceeds #MAX_STAT_CLIENTS
+ */
+int stat_client_add(int fd)
+{
+       struct stat_client *new_client;
+
+       if (num_clients >= MAX_STAT_CLIENTS) {
+               PARA_ERROR_LOG("maximal number of stat clients (%d) exceeded\n",
+                       MAX_STAT_CLIENTS);
+               return -E_TOO_MANY_CLIENTS;
+       }
+       if (!initialized) {
+               INIT_LIST_HEAD(&client_list);
+               initialized = 1;
+       }
+       PARA_INFO_LOG("adding client on fd %d\n", fd);
+       new_client = para_malloc(sizeof(struct stat_client));
+       new_client->fd = fd;
+       add_close_on_fork_list(fd);
+       list_add(&new_client->node, &client_list);
+       dump_stat_client_list();
+       num_clients++;
+       return 1;
+}
+/**
+ * write a message to all connected stat clients
+ *
+ * \param msg a \p NULL terminated buffer
+ */
+void stat_client_write(char *msg)
+{
+       struct stat_client *sc, *tmp;
+       char *buf;
+       ssize_t len;
+       struct timeval tv = {0 , 0};
+
+       if (!initialized)
+               return;
+       buf = make_message("%s\n", msg);
+       len = strlen(buf);
+       list_for_each_entry_safe(sc, tmp, &client_list, node) {
+               int fd = sc->fd, ret;
+               fd_set wfds;
+               FD_ZERO(&wfds);
+               FD_SET(fd, &wfds);
+//             PARA_DEBUG_LOG("%s: p=%lx\n", __func__, (long)p);
+               do
+                       ret = select(fd + 1, NULL, &wfds, NULL, &tv);
+               while (ret < 0 && errno == EINTR);
+               if (ret) {
+                       ret = write(fd, buf, len);
+                       PARA_DEBUG_LOG("dumped %s to fd %d, ret = %d\n", msg, fd, ret);
+                       if (ret == len )
+                               continue;
+               }
+               /* write error or fd not ready for writing */
+               close(fd);
+               del_close_on_fork_list(fd);
+               num_clients--;
+               PARA_INFO_LOG("deleting client on fd %d\n", fd);
+               list_del(&sc->node);
+               free(sc);
+               dump_stat_client_list();
+       }
+       free(buf);
+       PARA_DEBUG_LOG("%d client(s)\n", num_clients);
+}
+
+/**
+ * send empty status list
+ *
+ * Send to  each connected client the full status item list
+ * with empty values.
+ */
+void dump_empty_status(void)
+{
+       int i;
+       char *empty_items = NULL;
+
+       if (!initialized)
+               return;
+       FOR_EACH_STAT_ITEM(i) {
+               char *tmp = make_message("%s%s:\n", empty_items? empty_items : "",
+                       status_item_list[i]);
+               free(empty_items);
+               empty_items = tmp;
+       }
+//     PARA_DEBUG_LOG("%s: empty items: %s\n", __func__, empty_items);
+       stat_client_write(empty_items);
+       free(empty_items);
+}
+
+/**
+ * check if line starts with known status item.
+ *
+ * \param line buffer containing the line
+ *
+ * \return If the beginning of \a line matches any paraslash status item and is
+ * followed by a colon, the number of that status item is returned. Otherwise,
+ * this function returns \p -E_UNKNOWN_STAT_ITEM.
+ */
+int stat_line_valid(const char *line)
+{
+       int i;
+       size_t line_len;
+
+       if (!line || !*line)
+               return -E_UNKNOWN_STAT_ITEM;
+       line_len = strlen(line);
+       for (i = 0; i < NUM_STAT_ITEMS; i++) {
+               const char *s = status_item_list[i];
+               size_t item_len = strlen(s);
+
+               if (line_len > item_len && line[item_len] == ':' &&
+                               !strncmp(line, s, item_len))
+                       return i;
+       }
+       return -E_UNKNOWN_STAT_ITEM;
+}
+
+/**
+ * call a custom function for each complete line
+ *
+ * \param buf the buffer containing data seperated by newlines
+ * \param n the number of bytes in \a buf
+ * \param line_handler the custom function
+ * \param num upper bound on calls to \a line_handler
+ *
+ * If \a line_handler is \p NULL, return number of complete lines in buf.
+ * Otherwise, call \a line_handler for each complete line, but no more than \a num
+ * times. If \a num is zero, there is no restriction on how often \a line_handler
+ * may be called. The rest of the buffer (last chunk containing incomplete line
+ * if \a num is zero) is moved to the beginning of the buffer.
+ *
+ * \return If line_handler is not NULL, this function returns the number
+ * of bytes not handled to \a line_handler.
+ */
+unsigned for_each_line(char *buf, int n, void (*line_handler)(char *), int num)
+{
+       char *start = buf, *end;
+       int i, num_lines = 0;
+
+       while (start < buf + n) {
+               char *next_null;
+               char *next_cr;
+
+               next_cr = memchr(start, '\n', buf + n - start);
+               next_null = memchr(start, '\0', buf + n - start);
+               if (!next_cr && !next_null)
+                       break;
+               if (next_cr && next_null) {
+                       end = next_cr < next_null? next_cr : next_null;
+               } else if (next_null) {
+                       end = next_null;
+               } else
+                       end = next_cr;
+               num_lines++;
+               if (line_handler) {
+                       *end = '\0';
+                       line_handler(start);
+                       start = ++end;
+                       if (num && num_lines >= num)
+                               break;
+               } else
+                       start = ++end;
+       }
+       if (!line_handler)
+               return num_lines;
+       i = buf + n - start;
+       if (i && i != n)
+               memmove(buf, start, i);
+       return i;
+}
+
diff --git a/string.c b/string.c
new file mode 100644 (file)
index 0000000..07f54a0
--- /dev/null
+++ b/string.c
@@ -0,0 +1,447 @@
+/*
+ * Copyright (C) 2004-2006 Andre Noll <maan@systemlinux.org>
+ *
+ *     This program is free software; you can redistribute it and/or modify
+ *     it under the terms of the GNU General Public License as published by
+ *     the Free Software Foundation; either version 2 of the License, or
+ *     (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU General Public License for more details.
+ *
+ *     You should have received a copy of the GNU General Public License
+ *     along with this program; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ */
+
+/** \file string.c memory allocation and string handling functions */
+
+#include <sys/time.h> /* gettimeofday */
+#include "para.h"
+#include <regex.h>
+#include <pwd.h>
+#include <sys/utsname.h> /* uname() */
+#include "error.h"
+#include "string.h"
+
+/**
+ * paraslash's version of realloc()
+ *
+ * \param p pointer to the memory block, may be NULL
+ * \param size desired new size
+ *
+ * A wrapper for realloc(3). It calls \p exit(\p EXIT_FAILURE) on errors,
+ * i.e. there is no need to check the return value in the caller.
+ * \sa realloc(3)
+ */
+__must_check __malloc void *para_realloc(void *p, size_t size)
+{
+       /*
+        * No need to check for NULL pointers: If p is NULL, the  call
+        * to realloc is equivalent to malloc(size)
+        */
+       if (!(p = realloc(p, size))) {
+               PARA_EMERG_LOG("%s", "realloc failed, aborting\n");
+               exit(EXIT_FAILURE);
+       }
+       return p;
+}
+
+/**
+ * paraslash's version of malloc()
+ *
+ * \param size desired new size
+ *
+ * A wrapper for malloc(3) which exits on errors.
+ * \sa malloc(3)
+ */
+__must_check __malloc void *para_malloc(size_t size)
+{
+       void *p = malloc(size);
+
+       if (!p) {
+               PARA_EMERG_LOG("%s", "malloc failed, aborting\n");
+               exit(EXIT_FAILURE);
+       }
+       return p;
+}
+
+/**
+ * paraslash's version of calloc()
+ *
+ * \param size desired new size
+ *
+ * A wrapper for calloc(3) which exits on errors.
+ * \sa calloc(3)
+ */
+__must_check __malloc void *para_calloc(size_t size)
+{
+       void *ret = para_malloc(size);
+
+       memset(ret, 0, size);
+       return ret;
+}
+
+/**
+ * paraslash's version of strdup()
+ *
+ * \param s: string to be duplicated
+ *
+ * A wrapper for strdup(3). It calls exit(EXIT_FAILURE) on
+ * errors, i.e. there is no need to check the return value in the caller.
+ * Moreover, this wrapper checks for \a s being NULL and returns an empty
+ * string in this case.
+ *
+ * \sa strdup(3)
+ */
+__must_check __malloc char *para_strdup(const char *s)
+{
+       char *ret;
+
+       if ((ret = strdup(s? s: "")))
+               return ret;
+       PARA_EMERG_LOG("%s", "strdup failed, aborting\n");
+       exit(EXIT_FAILURE);
+}
+
+/**
+ * allocate a sufficiently large string and print into it
+ *
+ * \param fmt usual format string
+ *
+ * Produce output according to \a fmt. No artificial bound on the length of the
+ * resulting string is imposed. This function either returns a pointer to a
+ * string that must be freed by the caller or aborts without returning.
+ *
+ * \sa printf(3)
+ */
+__must_check __printf_1_2 __malloc char *make_message(const char *fmt, ...)
+{
+       char *msg;
+
+       PARA_VSPRINTF(fmt, msg);
+       return msg;
+}
+
+/**
+ * paraslash's version of strcat()
+ *
+ * \param a string to be appended to
+ * \param b string to append
+ *
+ * Append \a b to \a a. If \a a is NULL, return a copy of \a b, i.e.
+ * para_strcat(NULL, b) is equivalent to para_strdup(b).  If \a b is NULL,
+ * return \a a without making a copy of \a a.  Otherwise, construct the
+ * concatenation \a c, free \a a (but not \a b) and return \a c.
+ *
+ * \sa strcat(3)
+ */
+__must_check __malloc char *para_strcat(char *a, const char *b)
+{
+       char *tmp;
+
+       if (!a)
+               return para_strdup(b);
+       if (!b)
+               return a;
+       tmp = make_message("%s%s", a, b);
+       free(a);
+       return tmp;
+}
+
+/**
+ * paraslash's version of dirname()
+ *
+ * \param name pointer to The full path
+ *
+ * If \a name is \þ NULL or the empty string, return \p NULL, Otherwise, Make a
+ * copy of \a name and return its directory component. Caller is responsible to
+ * free the result.
+ */
+__must_check __malloc char *para_dirname(const char *name)
+{
+       char *p, *ret;
+
+       if (!name || !*name)
+               return NULL;
+       ret = para_strdup(name);
+       p = strrchr(ret, '/');
+       if (!p)
+               *ret = '\0';
+       else
+               *p = '\0';
+       return ret;
+}
+
+/**
+ * paraslash's version of basename()
+ *
+ * \param name Pointer to the full path
+ *
+ * If \a name is \p NULL or the empty string, return \p NULL, Otherwise, make a
+ * copy of \a name and return its filename component. Caller is responsible to
+ * free the result.
+ */
+__must_check __malloc char *para_basename(const char *name)
+{
+       char *p;
+
+       if (!name || !*name)
+               return NULL;
+       p = strrchr(name, '/');
+       if (!p)
+               return para_strdup(name);
+       p++;
+       if (!*p)
+               return NULL;
+       return para_strdup(p);
+}
+
+/**
+ * simple search and replace routine
+ *
+ * \param src source string
+ * \param macro_name the name of the macro
+ * \param replacement the replacement format string
+ *
+ * Replace \a macro_name(arg) by \a replacement. \a replacement is a format
+ * string which may contain a single string conversion specifier which gets
+ * replaced by 'arg'.
+ *
+ * \return A string in which all matches in \a src are replaced, or NULL if \a
+ * macro_name was not found in \a src.  Caller must free the result.
+ *
+ * \sa regcomp(3)
+ */
+__must_check __malloc char *s_a_r(const char *src, const char* macro_name,
+               const char *replacement)
+{
+       regex_t preg;
+       size_t  nmatch = 1;
+       regmatch_t pmatch[1];
+       int eflags = 0;
+       char *dest = NULL;
+       const char *bufptr = src;
+
+       if (!macro_name || !replacement || !src)
+               return para_strdup(src);
+       regcomp(&preg, macro_name, 0);
+       while (regexec(&preg,  bufptr, nmatch, pmatch, eflags)
+                       != REG_NOMATCH) {
+               char *tmp, *arg, *o_bracket, *c_bracket;
+
+               o_bracket = strchr(bufptr + pmatch[0].rm_so, '(');
+               c_bracket = o_bracket? strchr(o_bracket, ')') : NULL;
+               if (!c_bracket)
+                       goto out;
+               tmp = para_strdup(bufptr);
+               tmp[pmatch[0].rm_so] = '\0';
+               dest = para_strcat(dest, tmp);
+               free(tmp);
+
+               arg = para_strdup(o_bracket + 1);
+               arg[c_bracket - o_bracket - 1] = '\0';
+               tmp = make_message(replacement, arg);
+               free(arg);
+               dest = para_strcat(dest, tmp);
+               free(tmp);
+               bufptr = c_bracket;
+               bufptr++;
+       }
+       dest = para_strcat(dest, bufptr);
+//     PARA_DEBUG_LOG("%s: returning %s\n", __func__, dest);
+out:
+       regfree(&preg);
+       return dest;
+}
+
+/**
+ * replace a string according to a list of macros
+ *
+ * \param macro_list the array containing a macro/replacement pairs.
+ * \param src the source string
+ *
+ * This function just calls s_a_r() for each element of \a macro_list.
+ */
+__must_check __malloc char *s_a_r_list(struct para_macro *macro_list, char *src)
+{
+       struct para_macro *mp = macro_list;
+       char *ret = NULL, *tmp = para_strdup(src);
+
+       while (mp->name) {
+               ret = s_a_r(tmp, mp->name, mp->replacement);
+               free(tmp);
+               if (!ret)
+                       return src; /* FIXME: shouldn't we continue here? */
+               tmp = ret;
+               mp++;
+       }
+       //PARA_DEBUG_LOG("%s: returning %s\n", __func__, dest);
+       return ret;
+}
+
+/**
+ * cut trailing newline
+ *
+ * \param buf the string to be chopped.
+ *
+ * Replace the last character in \a buf by zero if it is euqal to
+ * the newline character.
+ */
+void chop(char *buf)
+{
+       int n = strlen(buf);
+       if (!n)
+               return;
+       if (buf[n - 1] == '\n')
+               buf[n - 1] = '\0';
+}
+
+/**
+ * get a random filename
+ *
+ * This is by no means a secure way to create temporary files in a hostile
+ * direcory like /tmp. However, it is OK to use for temp files, fifos, sockets
+ * that are created in ~/.paraslash. Result must be freed by the caller.
+ */
+__must_check __malloc char *para_tmpname(void)
+{
+       struct timeval now;
+       gettimeofday(&now, NULL);
+       srand(now.tv_usec);
+       return make_message("%08i", rand());
+}
+
+/**
+ * create unique temporary file
+ *
+ * \param template the template to be passed to mkstemp()
+ * \param mode the desired mode of the tempfile
+ *
+ * This wrapper for mkstemp additionally uses fchmod() to
+ * set the given mode of the tempfile if mkstemp() returned success.
+ * Return value: The file descriptor of the temp file just created on success.
+ * On errors, -E_MKSTEMP or -E_FCHMOD is returned.
+ */
+__must_check int para_mkstemp(char *template, mode_t mode)
+{
+       int tmp, fd = mkstemp(template);
+
+       if (fd < 0)
+               return -E_MKSTEMP;
+       tmp = fchmod(fd, mode);
+       if (tmp >= 0)
+               return fd;
+       close(fd);
+       unlink(template);
+       return -E_FCHMOD;
+}
+
+/**
+ * get the logname of the current user
+ *
+ * \return A dynammically allocated string that must be freed by the caller. On
+ * errors, the string "unknown user" is returned, i.e. this function never
+ * returns NULL.
+ */
+__must_check __malloc char *para_logname(void)
+{
+       struct passwd *pw = getpwuid(getuid());
+       return para_strdup(pw? pw->pw_name : "unknown_user");
+}
+
+/**
+ * get the home directory of the current user
+ *
+ * \return A dynammically allocated string that must be freed by the caller. If
+ * the home directory could not be found, this function returns "/tmp".
+ */
+__must_check __malloc char *para_homedir(void)
+{
+       struct passwd *pw = getpwuid(getuid());
+       return para_strdup(pw? pw->pw_dir : "/tmp");
+}
+
+/**
+ * split string and return pointers to its parts.
+ *
+ * \param args the string to be split
+ * \param argv_ptr  pointer to the list of substrings
+ * \param delim delimiter
+ *
+ * This function modifies \a args by replacing each occurance of \a delim by
+ * zero. A NULL-terminated array of pointers to char* is allocated dynamically
+ * and these pointers are initialized to point to the broken-up substrings
+ * within \a args. A pointer to this array is returned via \a argv_ptr.
+ *
+ * \return The number of substrings found in \a args.
+ */
+__must_check unsigned split_args(char *args, char ***argv_ptr, int delim)
+{
+       char *p = args;
+       char **argv;
+       ssize_t n = 0, i;
+
+       while ((p = strchr(p, delim))) {
+               p++;
+               n++;
+       }
+       *argv_ptr = para_malloc((n + 3) * sizeof(char *));
+       argv = *argv_ptr;
+       i = 0;
+       p = args;
+//     printf("split_args: a:%s\n", p);
+       while (p) {
+               argv[i] = p;
+               p = strchr(p, delim);
+               if (p) {
+//             printf("a:%s\n", p);
+                       *p = '\0';
+//             printf("b:\n");
+                       p++;
+                       i++;
+               }
+       }
+       argv[n + 1] = NULL;
+       return n;
+}
+
+/**
+ * ensure that file descriptors 0, 1, and 2 are valid
+ *
+ * Common approach that opens /dev/null until it gets a file descriptor greater
+ * than two.
+ *
+ * \sa okir's Black Hats Manual.
+ */
+void valid_fd_012(void)
+{
+       while (1) {
+       int     fd;
+
+               fd = open("/dev/null", O_RDWR);
+               if (fd < 0)
+                       exit(EXIT_FAILURE);
+               if (fd > 2) {
+                       close(fd);
+                       break;
+               }
+       }
+}
+
+/**
+ * get the own hostname
+ *
+ * \return A dynammically allocated string containing the hostname.
+ *
+ * \sa uname(2)
+ */
+__malloc char *para_hostname(void)
+{
+       struct utsname u;
+
+       uname(&u);
+       return para_strdup(u.nodename);
+}
diff --git a/string.h b/string.h
new file mode 100644 (file)
index 0000000..9fb42b2
--- /dev/null
+++ b/string.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2006 Andre Noll <maan@systemlinux.org>
+ *
+ *     This program is free software; you can redistribute it and/or modify
+ *     it under the terms of the GNU General Public License as published by
+ *     the Free Software Foundation; either version 2 of the License, or
+ *     (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU General Public License for more details.
+ *
+ *     You should have received a copy of the GNU General Public License
+ *     along with this program; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ */
+
+/** \file string.h exported sybmols from string.c */
+
+/** contains name/replacement pairs used by s_a_r_list()
+ *
+ * \sa s_a_r()
+ */
+struct para_macro {
+       /** the name of the macro */
+       char *name;
+       /** the replacement text */
+       char *replacement;
+};
+__must_check __malloc void *para_realloc(void *p, size_t size);
+__must_check __malloc void *para_malloc(size_t size);
+__must_check __malloc void *para_calloc(size_t size);
+__must_check __malloc char *para_strdup(const char *s);
+__must_check __malloc __printf_1_2 char *make_message(const char *fmt, ...);
+__must_check __malloc char *para_strcat(char *a, const char *b);
+__must_check __malloc char *para_dirname(const char *name);
+__must_check __malloc char *para_basename(const char *name);
+__must_check __malloc char *s_a_r(const char *src, const char* regex, const char *replacement);
+__must_check __malloc char *s_a_r_list(struct para_macro *pm, char *src);
+void chop(char* buf);
+__must_check __malloc char *para_tmpname(void);
+__must_check int para_mkstemp(char *template, mode_t mode);
+__must_check __malloc char *para_logname(void);
+__must_check __malloc char *para_homedir(void);
+__must_check unsigned split_args(char *args, char ***argv_ptr, int delim);
+__malloc char *para_hostname(void);
+void valid_fd_012(void);
+
diff --git a/time.c b/time.c
new file mode 100644 (file)
index 0000000..f659b16
--- /dev/null
+++ b/time.c
@@ -0,0 +1,165 @@
+#include "para.h"
+/** \file time.c helper functions for dealing with timeouts */
+
+/**
+ * convert struct timeval to milliseconds
+ *
+ * \param tv the value to convert
+ *
+ * \return The number off milliseconds in \a tv
+ */
+long unsigned tv2ms(const struct timeval *tv)
+{
+       return tv->tv_sec * 1000 + (tv->tv_usec + 500)/ 1000;
+}
+
+/**
+ * convert milliseconds to struct timeval
+ *
+ * \param n the number of milliseconds
+ * \param tv will be filled in by the function
+ */
+void ms2tv(long unsigned n, struct timeval *tv)
+{
+       tv->tv_sec = n / 1000;
+       tv->tv_usec = (n % 1000) * 1000;
+}
+
+/**
+ * convert double to struct timeval
+ *
+ * \param x the value to convert
+ * \param tv converted value is stored here
+ */
+void d2tv(double x, struct timeval *tv)
+{
+       tv->tv_sec = x;
+       tv->tv_usec = (x - (double)tv->tv_sec) * 1000.0 * 1000.0 + 0.5;
+}
+
+/**
+ * compute the difference of two time values
+ *
+ * \param b minuend
+ * \param a subtrahend
+ * \param diff difference is stored here
+ *
+ * \return If b > a, compute b - a and return 1. if b < a
+ * compute a - b and return -1. If \a diff is not  \p NULL, \a diff gets
+ * filled in with the absolute value |b - a|.
+ */
+int tv_diff(const struct timeval *b, const struct timeval *a, struct timeval *diff)
+{
+       int ret = 1;
+
+       if ((b->tv_sec < a->tv_sec) ||
+               ((b->tv_sec == a->tv_sec) && (b->tv_usec < a->tv_usec))) {
+               const struct timeval *tmp = a;
+               a = b;
+               b = tmp;
+               ret = -1;
+       }
+       if (!diff)
+               return ret;
+       diff->tv_sec = b->tv_sec - a->tv_sec;
+       if (b->tv_usec < a->tv_usec) {
+               diff->tv_sec--;
+               diff->tv_usec = 1000 * 1000 - a->tv_usec + b->tv_usec;
+       } else
+               diff->tv_usec = b->tv_usec - a->tv_usec;
+       return ret;
+}
+
+/**
+ * add two structs of type timeval
+ *
+ * \param a first addend
+ * \param b second addend
+ * \param sum holds the sum upon return
+ */
+void tv_add(const struct timeval *a, const struct timeval *b,
+       struct timeval *sum)
+{
+       sum->tv_sec = a->tv_sec + b->tv_sec;
+       if (a->tv_usec + b->tv_usec >= 1000 * 1000) {
+               sum->tv_sec++;
+               sum->tv_usec = a->tv_usec + b->tv_usec - 1000 * 1000;
+       } else
+               sum->tv_usec = a->tv_usec + b->tv_usec;
+}
+
+/**
+ * compute integer multiple of given struct timeval
+ *
+ * \param mult the integer value to multiply with
+ * \param tv the timevalue to multiply
+ * \param result holds mult * tv upon return
+ */
+void tv_scale(const unsigned long mult, const struct timeval *tv,
+       struct timeval *result)
+{
+       result->tv_sec = mult * tv->tv_sec;
+       result->tv_sec += tv->tv_usec * mult / 1000 / 1000;
+       result->tv_usec = tv->tv_usec * mult % (1000 * 1000);
+}
+
+/**
+ * compute fraction of given struct timeval
+ *
+ * \param div the integer value to divide by
+ * \param tv the timevalue to divide
+ * \param result holds (1 / mult) * tv upon return
+ */
+void tv_divide(const unsigned long div, const struct timeval *tv,
+       struct timeval *result)
+{
+       long unsigned q = tv->tv_usec / div;
+       result->tv_sec = tv->tv_sec / div;
+       result->tv_usec = (tv->tv_sec - result->tv_sec * div) * 1000 * 1000 / div;
+       if (result->tv_usec + q >= 1000 * 1000) {
+               result->tv_sec++;
+               result->tv_usec = 1000 * 1000 - result->tv_usec - q;
+       } else
+               result->tv_usec += q;
+}
+
+/**
+ * compute a convex combination of two time values
+ *
+ * \param a the first coefiicent
+ * \param tv1 the first time value
+ * \param b the second coefiicent
+ * \param tv2 the second time value
+ * \param result holds the convex combination upon return
+ *
+ * compute x := (a * tv1 + b * tv2) / (|a| + |b|). a, b may be negative.
+ * returns sign(x) and stores |x| in the result pointer.
+ */
+int tv_convex_combination(const long a, const struct timeval *tv1,
+               const long b, const struct timeval *tv2,
+               struct timeval *result)
+{
+       struct timeval tmp1, tmp2, tmp3;
+       int ret = 1, subtract = ((a > 0 && b < 0) || (a < 0 && b > 0));
+       unsigned long a1 = ABS(a), b1 = ABS(b);
+
+       tv_scale(a1, tv1, &tmp1);
+       tv_scale(b1, tv2, &tmp2);
+       if (subtract)
+               ret = tv_diff(&tmp1, &tmp2, &tmp3);
+       else
+               tv_add(&tmp1, &tmp2, &tmp3);
+       if (a1 + b1)
+               tv_divide(a1 + b1, &tmp3, result);
+       else {
+               result->tv_sec = 0;
+               result->tv_usec = 0;
+       }
+       if (!a || !b) {
+               if (a + b < 0)
+                       ret = -1;
+       } else
+               if (a < 0)
+                       ret = -ret;
+       return ret;
+}
diff --git a/versions/paraslash-0.0.99.tgz b/versions/paraslash-0.0.99.tgz
new file mode 100644 (file)
index 0000000..0dc68c5
Binary files /dev/null and b/versions/paraslash-0.0.99.tgz differ
diff --git a/versions/paraslash-0.0.99.tgz.asc b/versions/paraslash-0.0.99.tgz.asc
new file mode 100644 (file)
index 0000000..2f50390
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.2.5 (GNU/Linux)
+
+iD8DBQBBcp6/Wto1QDEAkw8RAiLVAKCI8AyDm8nnIlUVTdMjPXvFiiigYwCgpcbP
+yRYrzN5SJeJGB9BMQwy93FA=
+=9IMv
+-----END PGP SIGNATURE-----
diff --git a/versions/paraslash-0.1.0.tgz b/versions/paraslash-0.1.0.tgz
new file mode 100644 (file)
index 0000000..b063c8d
Binary files /dev/null and b/versions/paraslash-0.1.0.tgz differ
diff --git a/versions/paraslash-0.1.0.tgz.asc b/versions/paraslash-0.1.0.tgz.asc
new file mode 100644 (file)
index 0000000..d4dc3e7
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.2.5 (GNU/Linux)
+
+iD8DBQBBeNU/Wto1QDEAkw8RAk+fAJ9gcyVaAWC80zkBBrIOn+xe1PYa5gCfeDkl
+mLT5IExWAQLATFGpfoXjwgY=
+=8MrY
+-----END PGP SIGNATURE-----
diff --git a/versions/paraslash-0.1.1.tgz b/versions/paraslash-0.1.1.tgz
new file mode 100644 (file)
index 0000000..108e351
Binary files /dev/null and b/versions/paraslash-0.1.1.tgz differ
diff --git a/versions/paraslash-0.1.1.tgz.asc b/versions/paraslash-0.1.1.tgz.asc
new file mode 100644 (file)
index 0000000..64dc069
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.2.5 (GNU/Linux)
+
+iD8DBQBBi4NJWto1QDEAkw8RAo8LAJ9QorQ8F1/XAs3w124O2vrZYREFmgCgkm1c
+8Wk5758Y3KXL/gqhZ30FwYk=
+=rA4D
+-----END PGP SIGNATURE-----
diff --git a/versions/paraslash-0.1.2.tgz b/versions/paraslash-0.1.2.tgz
new file mode 100644 (file)
index 0000000..93de422
Binary files /dev/null and b/versions/paraslash-0.1.2.tgz differ
diff --git a/versions/paraslash-0.1.2.tgz.asc b/versions/paraslash-0.1.2.tgz.asc
new file mode 100644 (file)
index 0000000..02f01d2
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.2.5 (GNU/Linux)
+
+iD8DBQBBqjEFWto1QDEAkw8RAhlHAJ0Qncfz/82teh5tNXHcH2iJ31uXiwCggoeo
+A+pdWH4KH8gR3bETOR0vKY8=
+=8KSZ
+-----END PGP SIGNATURE-----
diff --git a/versions/paraslash-0.1.3.tgz b/versions/paraslash-0.1.3.tgz
new file mode 100644 (file)
index 0000000..1314d45
Binary files /dev/null and b/versions/paraslash-0.1.3.tgz differ
diff --git a/versions/paraslash-0.1.3.tgz.asc b/versions/paraslash-0.1.3.tgz.asc
new file mode 100644 (file)
index 0000000..b94011e
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.2.5 (GNU/Linux)
+
+iD8DBQBBuYcDWto1QDEAkw8RAv4+AJ9Cg+RVDyGwVJQrJ4/pyslvEw5ENwCdGe+p
+2k+K+awrnDwEiR8toLeKzg8=
+=5703
+-----END PGP SIGNATURE-----
diff --git a/versions/paraslash-0.1.4.tgz b/versions/paraslash-0.1.4.tgz
new file mode 100644 (file)
index 0000000..b98c682
Binary files /dev/null and b/versions/paraslash-0.1.4.tgz differ
diff --git a/versions/paraslash-0.1.4.tgz.asc b/versions/paraslash-0.1.4.tgz.asc
new file mode 100644 (file)
index 0000000..81432d8
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.2.5 (GNU/Linux)
+
+iD8DBQBBxaDnWto1QDEAkw8RAo4HAKCUoXYmraULQ3j1Vpskdp+mxrv8vQCdE2r2
+QkqQRJvrwih7YjOoZsgZNyo=
+=ZlPC
+-----END PGP SIGNATURE-----
diff --git a/versions/paraslash-0.1.5.tgz b/versions/paraslash-0.1.5.tgz
new file mode 100644 (file)
index 0000000..59f85fa
Binary files /dev/null and b/versions/paraslash-0.1.5.tgz differ
diff --git a/versions/paraslash-0.1.5.tgz.asc b/versions/paraslash-0.1.5.tgz.asc
new file mode 100644 (file)
index 0000000..fc717b8
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.2.5 (GNU/Linux)
+
+iD8DBQBB1YTyWto1QDEAkw8RAsrnAKCChqFSupAeJ6KxN/zu45tmLgDOawCfY2Sv
+oQvFqa+fnW4Sok8QIQTEHVc=
+=1btl
+-----END PGP SIGNATURE-----
diff --git a/versions/paraslash-0.1.6.tgz b/versions/paraslash-0.1.6.tgz
new file mode 100644 (file)
index 0000000..5c55cd2
Binary files /dev/null and b/versions/paraslash-0.1.6.tgz differ
diff --git a/versions/paraslash-0.1.6.tgz.asc b/versions/paraslash-0.1.6.tgz.asc
new file mode 100644 (file)
index 0000000..0811d45
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.2.5 (GNU/Linux)
+
+iD8DBQBCKbbOWto1QDEAkw8RAi8cAJkB0J1J917amcXhaIGrPOEwsh3N6wCffIkg
+0SbjPqZmjj0ysd07nkbfF9Y=
+=Tpn/
+-----END PGP SIGNATURE-----
diff --git a/versions/paraslash-0.1.7.tgz b/versions/paraslash-0.1.7.tgz
new file mode 100644 (file)
index 0000000..4d2b584
Binary files /dev/null and b/versions/paraslash-0.1.7.tgz differ
diff --git a/versions/paraslash-0.1.7.tgz.asc b/versions/paraslash-0.1.7.tgz.asc
new file mode 100644 (file)
index 0000000..6baf00f
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.2.5 (GNU/Linux)
+
+iD8DBQBCY+EgWto1QDEAkw8RAnKpAKCNDVXhmRbovvbtPh9B839k0I4UTACfTcM1
+37Omc/mYNF5t9RWCTDZXzMw=
+=UGev
+-----END PGP SIGNATURE-----
diff --git a/versions/paraslash-0.2.0.tar.bz2 b/versions/paraslash-0.2.0.tar.bz2
new file mode 100644 (file)
index 0000000..88e6a08
Binary files /dev/null and b/versions/paraslash-0.2.0.tar.bz2 differ
diff --git a/versions/paraslash-0.2.0.tar.bz2.asc b/versions/paraslash-0.2.0.tar.bz2.asc
new file mode 100644 (file)
index 0000000..60ed064
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.2 (GNU/Linux)
+
+iD8DBQBC9MmQWto1QDEAkw8RAicGAJ4+v+FAAuMkRp5ldeJOogt0HVz7YwCggaU1
+9X5kv752BC04Rnw9Z5gnNkk=
+=liTM
+-----END PGP SIGNATURE-----
diff --git a/versions/paraslash-0.2.1.tar.bz2 b/versions/paraslash-0.2.1.tar.bz2
new file mode 100644 (file)
index 0000000..51d1ff5
Binary files /dev/null and b/versions/paraslash-0.2.1.tar.bz2 differ
diff --git a/versions/paraslash-0.2.1.tar.bz2.asc b/versions/paraslash-0.2.1.tar.bz2.asc
new file mode 100644 (file)
index 0000000..b9dfb66
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.2 (GNU/Linux)
+
+iD8DBQBDAOfPWto1QDEAkw8RAkogAKCq1jAc5CzjCnRzzhRKvQJah2ZSOQCfSXjO
+1emlvg4SXyru3GibEDufnzo=
+=Rt/T
+-----END PGP SIGNATURE-----
diff --git a/versions/paraslash-0.2.10.tar.bz2 b/versions/paraslash-0.2.10.tar.bz2
new file mode 100644 (file)
index 0000000..4ba7cb9
Binary files /dev/null and b/versions/paraslash-0.2.10.tar.bz2 differ
diff --git a/versions/paraslash-0.2.10.tar.bz2.asc b/versions/paraslash-0.2.10.tar.bz2.asc
new file mode 100644 (file)
index 0000000..3c39ddc
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.2.1 (GNU/Linux)
+
+iD8DBQBD9lLgWto1QDEAkw8RAg/NAJ9CbDqJWcC6T6eUxkwJXUzLO+fIuwCfUvOX
+lDCOmC7Ekgfsj9bIn8DRgqg=
+=xqpS
+-----END PGP SIGNATURE-----
diff --git a/versions/paraslash-0.2.2.tar.bz2 b/versions/paraslash-0.2.2.tar.bz2
new file mode 100644 (file)
index 0000000..f2f042f
Binary files /dev/null and b/versions/paraslash-0.2.2.tar.bz2 differ
diff --git a/versions/paraslash-0.2.2.tar.bz2.asc b/versions/paraslash-0.2.2.tar.bz2.asc
new file mode 100644 (file)
index 0000000..92cc7d4
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.2 (GNU/Linux)
+
+iD8DBQBDBUN5Wto1QDEAkw8RAjUXAJ9nGvPGVUGU+ohYNuwBA3ntWYAV6gCeNWTS
+JruvcovSxpZ0/fGr47P3MX4=
+=BomB
+-----END PGP SIGNATURE-----
diff --git a/versions/paraslash-0.2.3.tar.bz2 b/versions/paraslash-0.2.3.tar.bz2
new file mode 100644 (file)
index 0000000..63c28da
Binary files /dev/null and b/versions/paraslash-0.2.3.tar.bz2 differ
diff --git a/versions/paraslash-0.2.3.tar.bz2.asc b/versions/paraslash-0.2.3.tar.bz2.asc
new file mode 100644 (file)
index 0000000..abc8b5d
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.2 (GNU/Linux)
+
+iD8DBQBDFygrWto1QDEAkw8RAhNwAJwMoTi7Z4UGJ8wOYQYemqKONC1D/gCfQL2e
+7UZ4vgK6SoPgfAF71KTiois=
+=Copg
+-----END PGP SIGNATURE-----
diff --git a/versions/paraslash-0.2.4.tar.bz2 b/versions/paraslash-0.2.4.tar.bz2
new file mode 100644 (file)
index 0000000..2340564
Binary files /dev/null and b/versions/paraslash-0.2.4.tar.bz2 differ
diff --git a/versions/paraslash-0.2.4.tar.bz2.asc b/versions/paraslash-0.2.4.tar.bz2.asc
new file mode 100644 (file)
index 0000000..585619c
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.2 (GNU/Linux)
+
+iD8DBQBDMYSLWto1QDEAkw8RAh9RAJ48UU3Ddo6vMaAKBCKlKixDzCMJkgCgioov
+4gIO9N9Of7Xe+x2uV/qVc4Q=
+=/C+7
+-----END PGP SIGNATURE-----
diff --git a/versions/paraslash-0.2.5.tar.bz2 b/versions/paraslash-0.2.5.tar.bz2
new file mode 100644 (file)
index 0000000..8f815dd
Binary files /dev/null and b/versions/paraslash-0.2.5.tar.bz2 differ
diff --git a/versions/paraslash-0.2.5.tar.bz2.asc b/versions/paraslash-0.2.5.tar.bz2.asc
new file mode 100644 (file)
index 0000000..991292c
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.2 (GNU/Linux)
+
+iD8DBQBDTq27Wto1QDEAkw8RAim+AKCS97CJGamp5zWyTE4TVFhcXAfsqQCfVY5F
+5u7hwO0LSFD7oken59eBaic=
+=Rc4P
+-----END PGP SIGNATURE-----
diff --git a/versions/paraslash-0.2.6.tar.bz2 b/versions/paraslash-0.2.6.tar.bz2
new file mode 100644 (file)
index 0000000..a9ba26e
Binary files /dev/null and b/versions/paraslash-0.2.6.tar.bz2 differ
diff --git a/versions/paraslash-0.2.6.tar.bz2.asc b/versions/paraslash-0.2.6.tar.bz2.asc
new file mode 100644 (file)
index 0000000..bdae38e
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.2 (GNU/Linux)
+
+iD8DBQBDY7w5Wto1QDEAkw8RAsigAJ9H7V6Ox+r8YCQtwyqpogvv7hn3VQCfW9oS
+kxHL+0dEFhh18oDFpFejUv4=
+=/wPn
+-----END PGP SIGNATURE-----
diff --git a/versions/paraslash-0.2.7.tar.bz2 b/versions/paraslash-0.2.7.tar.bz2
new file mode 100644 (file)
index 0000000..76cd8c4
Binary files /dev/null and b/versions/paraslash-0.2.7.tar.bz2 differ
diff --git a/versions/paraslash-0.2.7.tar.bz2.asc b/versions/paraslash-0.2.7.tar.bz2.asc
new file mode 100644 (file)
index 0000000..f7ad24a
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.2 (GNU/Linux)
+
+iD8DBQBDsYVZWto1QDEAkw8RAs4RAJ4hmW115pTB+eqI5HljZGAUxB9V4QCeOPRT
+/nrMniVfSJ2iHHU3jtZfYrs=
+=xiVS
+-----END PGP SIGNATURE-----
diff --git a/versions/paraslash-0.2.8.tar.bz2 b/versions/paraslash-0.2.8.tar.bz2
new file mode 100644 (file)
index 0000000..4aa7508
Binary files /dev/null and b/versions/paraslash-0.2.8.tar.bz2 differ
diff --git a/versions/paraslash-0.2.8.tar.bz2.asc b/versions/paraslash-0.2.8.tar.bz2.asc
new file mode 100644 (file)
index 0000000..72c2c8d
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.2 (GNU/Linux)
+
+iD8DBQBDuLAWWto1QDEAkw8RAo3JAJ47Q5nCQKinSIddlAhjoU4hxpNpKgCghMd/
+TSR7233HKIudTw/4/RLgRUg=
+=gaCj
+-----END PGP SIGNATURE-----
diff --git a/versions/paraslash-0.2.9.tar.bz2 b/versions/paraslash-0.2.9.tar.bz2
new file mode 100644 (file)
index 0000000..811387f
Binary files /dev/null and b/versions/paraslash-0.2.9.tar.bz2 differ
diff --git a/versions/paraslash-0.2.9.tar.bz2.asc b/versions/paraslash-0.2.9.tar.bz2.asc
new file mode 100644 (file)
index 0000000..682aa00
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.2 (GNU/Linux)
+
+iD8DBQBD1aQpWto1QDEAkw8RAnxcAJ4y5Bccey0e5U1I7QdTM1reZtAtGACfUrD6
+vQtvLz1h+ULsQKfgylvHn08=
+=7FUV
+-----END PGP SIGNATURE-----
diff --git a/wav.c b/wav.c
new file mode 100644 (file)
index 0000000..647be10
--- /dev/null
+++ b/wav.c
@@ -0,0 +1,108 @@
+/*
+ * 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 wav.c a filter that inserts a wave header */
+
+#include "gcc-compat.h"
+#include "para.h"
+
+#include "list.h"
+#include "filter.h"
+#include "string.h"
+
+#define WAV_OUTBUF_SIZE 81920
+#define WAV_HEADER_LEN 44
+#define BITS 16
+
+/* derived from oggdec.c of the vorbis-tools-1.0.1 package */
+#define WRITE_U32(buf, x) (buf)[0] = (unsigned char)((x) & 0xff);\
+       (buf)[1] = (unsigned char)(((x) >> 8) & 0xff);\
+       (buf)[2] = (unsigned char)(((x) >> 16) & 0xff);\
+       (buf)[3] = (unsigned char)(((x) >> 24) & 0xff);
+#define WRITE_U16(buf, x) (buf)[0] = (unsigned char)((x) & 0xff);
+
+static void make_wav_header(int channels, int samplerate, struct filter_node *fn)
+{
+
+       char *headbuf = fn->buf;
+       unsigned int size = 0x7fffffff;
+       int bytespersec = channels * samplerate * BITS / 8;
+       int align = channels * BITS / 8;
+
+       PARA_DEBUG_LOG("writing wave header: %d channels, %d KHz\n", channels, samplerate);
+       memset(headbuf, 0, WAV_HEADER_LEN);
+       memcpy(headbuf, "RIFF", 4);
+       WRITE_U32(headbuf + 4, size - 8);
+       memcpy(headbuf + 8, "WAVE", 4);
+       memcpy(headbuf + 12, "fmt ", 4);
+       WRITE_U32(headbuf + 16, 16);
+       WRITE_U16(headbuf + 20, 1);     /* format */
+       WRITE_U16(headbuf + 22, channels);
+       WRITE_U32(headbuf + 24, samplerate);
+       WRITE_U32(headbuf + 28, bytespersec);
+       WRITE_U16(headbuf + 32, align);
+       WRITE_U16(headbuf + 34, BITS);
+       memcpy(headbuf + 36, "data", 4);
+       WRITE_U32(headbuf + 40, size - 44);
+}
+
+static ssize_t wav_convert(char *inbuf, size_t len, struct filter_node *fn)
+{
+       size_t copy;
+       int *bof = fn->private_data;
+
+       if (*bof) {
+               make_wav_header(fn->fci->channels, fn->fci->samplerate, fn);
+               fn->loaded = WAV_HEADER_LEN;
+               *bof = 0;
+//             return 0;
+       }
+       copy = MIN(len, fn->bufsize - fn->loaded);
+       memmove(fn->buf + fn->loaded, inbuf, copy);
+       fn->loaded += copy;
+//     PARA_DEBUG_LOG("len = %d, copy = %d\n", len, copy);
+       return copy;
+}
+
+static void wav_close(struct filter_node *fn)
+{
+       free(fn->buf);
+       fn->buf = NULL;
+       free(fn->private_data);
+       fn->private_data = NULL;
+}
+
+static void wav_open(struct filter_node *fn)
+{
+       int *bof;
+
+       fn->bufsize = WAV_OUTBUF_SIZE;
+       fn->buf = para_malloc(fn->bufsize);
+       fn->private_data = para_malloc(sizeof(int));
+       bof = fn->private_data;
+       *bof = 1;
+       PARA_DEBUG_LOG("wav filter node: %p, output buffer: %p, loaded: %d\n",
+               fn, fn->buf, fn->loaded);
+}
+
+void wav_init(struct filter *f)
+{
+       f->convert = wav_convert;
+       f->close = wav_close;
+       f->open = wav_open;
+}
diff --git a/web/contact.in.html b/web/contact.in.html
new file mode 100644 (file)
index 0000000..6612507
--- /dev/null
@@ -0,0 +1,9 @@
+<h3>Contact</h3>
+
+<p> Andr&eacute; Noll, <a
+href="mailto:maan@systemlinux.org">maan@systemlinux.org</a> </p>
+
+Comments and bug reports are welcome (english, german, or spanish
+language). Please provide enough info such as the version of paraslash
+you are using and relevant parts of the logs. Including the string
+[paraslash] in the subject line is also a good idea.
diff --git a/web/demo.in.html b/web/demo.in.html
new file mode 100644 (file)
index 0000000..130e674
--- /dev/null
@@ -0,0 +1,28 @@
+       <h3><a name="live_demo">Live Demo</a></h3>
+
+       <p> There is a public paraslash stream at www.paraslash.org,
+       streaming
+
+               <a href="http://www.digitalvibes.de/">the music of Henri Petterson</a>.
+
+
+       You can listen to the stream with any mp3 player that supports
+       http streaming. Both </p>
+
+               <p>mpg123 -Z http://www.paraslash.org:8009/</p>
+
+       and
+
+               <p>xmms http://www.paraslash.org:8009/</p>
+
+       <p> are known to work.</p>
+
+       <p> Moreover, there is an anonymous paraslash account
+       available which you can use to have a look at paraslash
+       without configuring and running para_server on your own box.
+       Just download and run
+
+               <a href="demo-script">this shell script</a>
+
+       on your Unix system.  No root-privileges are required.</p>
+
diff --git a/web/documentation.in.html b/web/documentation.in.html
new file mode 100644 (file)
index 0000000..deaa45f
--- /dev/null
@@ -0,0 +1,25 @@
+<h3><a name="documentation">Documentation</a></h3>
+<p>
+Have a look at this
+       <a href="overview.pdf">overview</a>,
+a pdf file containing a sketch which illustrates how the pieces of paraslash work
+together. Read
+       <a href="README.html">README</a>
+for general information (including a list of required software),
+       <a href="INSTALL.html">INSTALL</a>
+for installation notes, and
+       <a href="README.mysql.html">README.mysql</a>
+for instructions on how to use the mysql database tool
+shipped with paraslash.</p>
+<p>
+The various commands of para_server and para_audiod are explained in
+paraslash's
+       <a href="doc/html/index.html">manual pages</a>.
+</p>
+<p>
+As of version 0.2.10, the source of para_server and para_audiod is fully documented.
+Have a look at the
+       <a href="doxygen/html/index.html">Paraslash API Reference</a>.
+</p>
+<p> Finally, you can <a href="HTML/index.html">RTFS online</a>.
+</p>
diff --git a/web/download.in.html b/web/download.in.html
new file mode 100644 (file)
index 0000000..fcdc1fe
--- /dev/null
@@ -0,0 +1,15 @@
+<h3><a name="download">Download</a></h3>
+
+<p> Only <a href="versions/">source</a> is available,
+including the <a href="versions/paraslash-cvs.tar.bz2">
+nightly cvs snapshot</a> (which may or may not compile at
+any given time). All regular releases are
+<a href="PUBLIC_KEY">cryptographically signed</a>.
+Anonymous (read-only) cvs access is also
+available. Checkout a copy with </p>
+
+<p> cvs -d
+:pserver:anonymous@cvs.systemlinux.org:/var/lib/cvs
+login </p>
+<p>(empty passwd)</p>
+<p> cvs -d :pserver:anonymous@cvs.systemlinux.org:/var/lib/cvs co paraslash </p>
diff --git a/web/footer.html b/web/footer.html
new file mode 100644 (file)
index 0000000..d9c9f05
--- /dev/null
@@ -0,0 +1,14 @@
+<hr>
+<!--
+       Last modified:
+       <!--#flastmod virtual="" -->
+       <p>
+       <a href="http://validator.w3.org/check?uri=referer"><img border="0"
+         src="http://www.w3.org/Icons/valid-html401"
+         alt="Valid HTML 4.01!" height="31" width="88"></a>
+       </p>
+-->
+       </td>
+       </table>
+</body>
+</html>
diff --git a/web/header.html b/web/header.html
new file mode 100644 (file)
index 0000000..c59f915
--- /dev/null
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+   "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+       <meta http-equiv='Content-Type' content='text/html; charset=iso-8859-1'>
+       <title>Paraslash</title>
+       <LINK href="para.css" REL="stylesheet" TYPE="text/css">
+       <link rel="shortcut icon" href="paraslash.ico">
+</head>
+<body>
+       <basefont face="lucida, helvetica, arial" size="3">
+       <table border="0" cellpadding="10" cellspacing="0">
+       <tr>
+               <td>
+                       <a href="/"><IMG SRC="paraslash.png" alt="paraslash" border="0"></a><BR>
+               </td>
+               <td>
+                       <h3>Paraslash: Play, archive, rate and stream
+                       large audio sets happily</h3>
+
+                       A set of tools for doing just what its name
+                       suggests.
+               </td>
+       </tr>
+       <tr>
+               <td valign="TOP">
+                       <br><a href="index.html">Home</a>
+                       <br><a href="NEWS.html">News</a>
+                       <br><a href="FEATURES.html">Features</a>
+                       <br><a href="download.html">Download</a>
+                       <br><a href="screenshots.html">Screenshots</a>
+                       <br><a href="demo.html">Live Demo</a>
+                       <br><a href="ChangeLog.html">Changelog</a>
+                       <br><a href="documentation.html">Documentation</a>
+                       <br><a href="license.html">License</a>
+                       <br><a href="contact.html">Contact</a>
+                       <br><a href="CREDITS.html">Credits</a>
+               </td>
+               <td Valign="TOP">
+       <hr>
diff --git a/web/index.in.html b/web/index.in.html
new file mode 100644 (file)
index 0000000..d2f28a4
--- /dev/null
@@ -0,0 +1,28 @@
+<h3>Events</h3>
+<ul>
+       <li>2006-02-17: <a href="versions/paraslash-0.2.10.tar.bz2">paraslash-0.2.10</a>
+       <a href="versions/paraslash-0.2.10.tar.bz2.asc">(sig)</a> "cyclic attractor"</li>
+       <li>2006-02-16: <a href="doxygen/html/index.html">API Reference</a> online</li>
+       <li>2006-01-24: paraslash-0.2.9 "progressive turbulence"</li>
+       <li>2006-01-02: paraslash-0.2.8 "dynamic accumulation"</li>
+       <li>2005-12-27: paraslash-0.2.7 "transparent invariance"</li>
+       <li>2005-11-12: new web pages</li>
+       <li>2005-10-29: paraslash-0.2.6 "recursive compensation"</li>
+       <li>2005-10-27: <a href="doc/html/index.html">manual pages</a> online</li>
+       <li>2005-10-13: paraslash-0.2.5 "aggressive resolution"</li>
+       <li>2005-09-21: paraslash-0.2.4 "toxic anticipation"</li>
+       <li>2005-09-01: paraslash-0.2.3 "hydrophilic movement"</li>
+       <li>2005-08-19: paraslash-0.2.2 "tangential excitation"</li>
+       <li>2005-08-15: paraslash-0.2.1 "surreal experience"</li>
+       <li>2005-08-06: overview.pdf</li>
+       <li>2005-08-06: paraslash-0.2.0 "distributed diffusion"</li>
+       <li>2005-08-01: paraslash live stream</li>
+       <li>2005-04-18: paraslash-0.1.7 "melting penetration"</li>
+       <li>2005-03-05: paraslash-0.1.6 "asymptotic balance"</li>
+       <li>2004-12-31: paraslash-0.1.5 "opaque eternity"</li>
+       <li>2004-12-19: paraslash-0.1.4 "tunneling transition"</li>
+       <li>2004-12-10: paraslash-0.1.3 "vanishing inertia"</li>
+       <li>2004-11-28: paraslash-0.1.2 "spherical fluctuation"</li>
+       <li>2004-11-05: paraslash-0.1.1 "floating atmosphere"</li>
+       <li>2004-10-22: paraslash-0.1.0 "rotating cortex"</li>
+</ul>
diff --git a/web/license.in.html b/web/license.in.html
new file mode 100644 (file)
index 0000000..055e436
--- /dev/null
@@ -0,0 +1,13 @@
+<h3><a name="license">License</a></h3>
+
+Paraslash is licensed under the <a
+href="http://www.gnu.org/licenses/gpl.html">GNU General
+Public License</a>, which is generally just abbreviated
+as the GPL license, or just the GPL. Check out the
+<a href="http://www.gnu.org/cgi-bin/license-quiz.cgi">GPL
+License Quiz</a> or take a look at the <a
+href="http://www.gnu.org/licenses/gpl-faq.html">Frequently Asked
+Questions about the GNU GPL</a>. For the differences between
+the established version 2 and the upcoming version 3 of the GPL, see this
+<a href="http://lia.ch/~sb/GPL_docdiff-2-3draft1.html"> colored
+diff</a>.
diff --git a/web/para.css b/web/para.css
new file mode 100644 (file)
index 0000000..62925ef
--- /dev/null
@@ -0,0 +1,310 @@
+BODY,H1,H2,H3,H4,H5,H6,P,CENTER,TD,TH,UL,DL,DIV {
+       font-family: Geneva, Arial, Helvetica, sans-serif;
+}
+BODY,TD {
+       font-size: 90%;
+}
+H1 {
+       text-align: center;
+       font-size: 160%;
+}
+H2 {
+       font-size: 120%;
+}
+H3 {
+       font-size: 100%;
+}
+CAPTION { font-weight: bold }
+DIV.qindex {
+       width: 100%;
+       background-color: #482e02;
+       border: 1px solid #84b007;
+       text-align: center;
+       margin: 2px;
+       padding: 2px;
+       line-height: 140%;
+}
+DIV.nav {
+       width: 100%;
+       background-color: #48ee02;
+       border: 1px solid #84b007;
+       text-align: center;
+       margin: 2px;
+       padding: 2px;
+       line-height: 140%;
+}
+DIV.navtab {
+       background-color: #48ee02;
+       border: 1px solid #84b007;
+       text-align: center;
+       margin: 2px;
+       margin-right: 15px;
+       padding: 2px;
+}
+TD.navtab {
+       font-size: 70%;
+}
+A.qindex {
+       text-decoration: none;
+       font-weight: bold;
+       color: #1A419D;
+}
+A.qindex:visited {
+       text-decoration: none;
+       font-weight: bold;
+       color: #1A419D
+}
+A.qindex:hover {
+       text-decoration: none;
+       background-color: #ffff00;
+}
+A.qindexHL {
+       text-decoration: none;
+       font-weight: bold;
+       background-color: #cccc00;
+       color: #000000;
+       border: 1px double #929502;
+}
+A.qindexHL:hover {
+       text-decoration: none;
+       background-color: #ffff00;
+       color: #000000;
+}
+A.qindexHL:visited { text-decoration: none; background-color: #e6c60c; color: #000000 }
+A.el { text-decoration: none; font-weight: bold }
+A.elRef { font-weight: bold }
+A.code:link { text-decoration: none; font-weight: normal; color: ##BA3708}
+A.code:visited { text-decoration: none; font-weight: normal; color: ##BA3708}
+A.codeRef:link { font-weight: normal; color: #BA3708}
+A.codeRef:visited { font-weight: normal; color: #BA3708}
+A:hover { text-decoration: none; background-color: #ffff00 }
+DL.el { margin-left: -1cm }
+.fragment {
+       font-family: Fixed, monospace;
+       font-size: 95%;
+}
+PRE.fragment {
+       border: 1px solid #CCCCCC;
+       background-color: #351505;
+       margin-top: 4px;
+       margin-bottom: 4px;
+       margin-left: 2px;
+       margin-right: 8px;
+       padding-left: 6px;
+       padding-right: 6px;
+       padding-top: 4px;
+       padding-bottom: 4px;
+}
+DIV.ah { background-color: black; font-weight: bold; color: #000000; margin-bottom: 3px; margin-top: 3px }
+TD.md { background-color: #112244; font-weight: bold; }
+TD.mdPrefix {
+       background-color: #112244;
+       color: #606060;
+       font-size: 80%;
+}
+TD.mdname1 { background-color: #112244; font-weight: bold; color: #00CC00; }
+TD.mdname { background-color: #112244; font-weight: bold; color: #00CC00; width: 600px; }
+DIV.groupHeader {
+       margin-left: 16px;
+       margin-top: 12px;
+       margin-bottom: 6px;
+       font-weight: bold;
+}
+DIV.groupText { margin-left: 16px; font-style: italic; font-size: 90% }
+BODY {
+       background: black;
+       color: #bbbbbb;
+       margin-right: 20px;
+       margin-left: 20px;
+}
+TD.indexkey {
+       background-color: #383e12;
+       font-weight: bold;
+       padding-right  : 10px;
+       padding-top    : 2px;
+       padding-left   : 10px;
+       padding-bottom : 2px;
+       margin-left    : 0px;
+       margin-right   : 0px;
+       margin-top     : 2px;
+       margin-bottom  : 2px;
+       border: 1px solid #CCCCCC;
+}
+TD.indexvalue {
+       background-color: #383e12;
+       font-style: italic;
+       padding-right  : 10px;
+       padding-top    : 2px;
+       padding-left   : 10px;
+       padding-bottom : 2px;
+       margin-left    : 0px;
+       margin-right   : 0px;
+       margin-top     : 2px;
+       margin-bottom  : 2px;
+       border: 1px solid #CCCCCC;
+}
+TR.memlist {
+   background-color: #112244; 
+}
+P.formulaDsp { text-align: center; }
+IMG.formulaDsp { }
+IMG.formulaInl { vertical-align: middle; }
+SPAN.keyword       { color: #008000 }
+SPAN.keywordtype   { color: #00CCCC }
+SPAN.keywordflow   { color: #108000 }
+SPAN.comment       { color: #00CCCC }
+SPAN.preprocessor  { color: #CC00CC }
+SPAN.stringliteral { color: #e0e020 }
+SPAN.charliteral   { color: #0000ff }
+.mdTable {
+       border: 1px solid #868686;
+       background-color: #112244;
+}
+.mdRow {
+       padding: 8px 10px;
+}
+.mdescLeft {
+       padding: 0px 8px 4px 8px;
+       font-size: 80%;
+       font-style: italic;
+       background-color: #000000;
+       border-top: 1px none #10E010;
+       border-right: 1px none #202010;
+       border-bottom: 1px none #202010;
+       border-left: 1px none #202010;
+       margin: 0px;
+}
+.mdescRight {
+       padding: 0px 8px 4px 8px;
+       font-size: 80%;
+       font-style: italic;
+       background-color: #000000;
+       border-top: 1px none #20E210;
+       border-right: 1px none #202010;
+       border-bottom: 1px none #202010;
+       border-left: 1px none #202010;
+       margin: 0px;
+}
+.memItemLeft {
+       padding: 1px 0px 0px 8px;
+       margin: 4px;
+       border-top-width: 1px;
+       border-right-width: 1px;
+       border-bottom-width: 1px;
+       border-left-width: 1px;
+       border-top-color: #FFFF10;
+       border-right-color: #FFFF10;
+       border-bottom-color: #FFFF10;
+       border-left-color: #FFFF10;
+       border-top-style: solid;
+       border-right-style: none;
+       border-bottom-style: none;
+       border-left-style: none;
+       background-color: #000000;
+       font-size: 80%;
+}
+.memItemRight {
+       padding: 1px 8px 0px 8px;
+       margin: 4px;
+       border-top-width: 1px;
+       border-right-width: 1px;
+       border-bottom-width: 1px;
+       border-left-width: 1px;
+       border-top-color: #E0E000;
+       border-right-color: #E0E000;
+       border-bottom-color: #E0E000;
+       border-left-color: #E0E000;
+       border-top-style: solid;
+       border-right-style: none;
+       border-bottom-style: none;
+       border-left-style: none;
+       background-color: #000000;
+       font-size: 80%;
+}
+.memTemplItemLeft {
+       padding: 1px 0px 0px 8px;
+       margin: 4px;
+       border-top-width: 1px;
+       border-right-width: 1px;
+       border-bottom-width: 1px;
+       border-left-width: 1px;
+       border-top-color: #E0E000;
+       border-right-color: #E0E000;
+       border-bottom-color: #E0E000;
+       border-left-color: #E0E000;
+       border-top-style: none;
+       border-right-style: none;
+       border-bottom-style: none;
+       border-left-style: none;
+       background-color: #000000;
+       font-size: 80%;
+}
+.memTemplItemRight {
+       padding: 1px 8px 0px 8px;
+       margin: 4px;
+       border-top-width: 1px;
+       border-right-width: 1px;
+       border-bottom-width: 1px;
+       border-left-width: 1px;
+       border-top-color: #E0E000;
+       border-right-color: #E0E000;
+       border-bottom-color: #E0E000;
+       border-left-color: #E0E000;
+       border-top-style: none;
+       border-right-style: none;
+       border-bottom-style: none;
+       border-left-style: none;
+       background-color: #000000;
+       font-size: 80%;
+}
+.memTemplParams {
+       padding: 1px 0px 0px 8px;
+       margin: 4px;
+       border-top-width: 1px;
+       border-right-width: 1px;
+       border-bottom-width: 1px;
+       border-left-width: 1px;
+       border-top-color: #E0E000;
+       border-right-color: #E0E000;
+       border-bottom-color: #E0E000;
+       border-left-color: #E0E000;
+       border-top-style: solid;
+       border-right-style: none;
+       border-bottom-style: none;
+       border-left-style: none;
+       color: #606060;
+       background-color: #3A3A1A;
+       font-size: 80%;
+}
+.search     { color: #f03309;
+              font-weight: bold;
+}
+FORM.search {
+              margin-bottom: 0px;
+              margin-top: 0px;
+}
+INPUT.search { font-size: 75%;
+               color: #800000;
+               font-weight: normal;
+               background-color: #000000;
+}
+TD.tiny      { font-size: 75%;
+}
+a {
+       color: #BA4108;
+}
+a:visited {
+       color: #BA3708;
+}
+.dirtab { padding: 4px;
+          border-collapse: collapse;
+          border: 1px solid #84b007;
+}
+TH.dirtab { background: #080e02;
+            font-weight: bold;
+}
+HR { height: 1px;
+     border: none;
+     border-top: 1px solid yellow;
+}
+
diff --git a/web/screenshots.in.html b/web/screenshots.in.html
new file mode 100644 (file)
index 0000000..6d14be1
--- /dev/null
@@ -0,0 +1,23 @@
+<h3><a name="license">Screenshots</a></h3>
+
+<p> The main part of paraslash is a client-server application, so
+it doesn't have much to take a screenshot of. However, here is the
+startup part of
+
+       <a href="para_server-startup.txt">para_server's logfile</a>
+
+and a similar thing for the logfile of
+
+       <a href="para_audiod-startup.txt">para_audiod</a>.</p>
+
+Moreover, there are screenshots of
+
+       <a href="gui-2005-11-12.png">para_gui</a>,
+
+       <a href="sdl_gui.jpg">para_sdl_gui</a>,
+
+       <a href="para_krell-2005-02.png">para_krell</a>
+
+and
+       <a href="para_slider-2004-12.png">para_slider</a>.
+