Let's try if this works out.
--- /dev/null
+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
+
+
--- /dev/null
+- lyrics
+- gui: bot window scrollable
--- /dev/null
+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.
+
--- /dev/null
+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])
--- /dev/null
+# 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
--- /dev/null
+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.
--- /dev/null
+ 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.
--- /dev/null
+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.
--- /dev/null
+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 - - < $< > $@
--- /dev/null
+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
--- /dev/null
+-----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-----
--- /dev/null
+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.
--- /dev/null
+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.
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/************************************************************************
+* 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 */
--- /dev/null
+/*
+ * 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++;
+}
--- /dev/null
+/*
+ * 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);
+
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+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
--- /dev/null
+/*
+ * 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();
+}
--- /dev/null
+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
--- /dev/null
+/** \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
+
--- /dev/null
+#!/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
--- /dev/null
+_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
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+# 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
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/** \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);
--- /dev/null
+/*
+ * 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;
+}
+