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;
+}
+
+/*
+ * check if perms are sufficient to exec a command having perms cmd_perms.
+ * Returns 0 if perms are sufficient, -E_PERM otherwise.
+ */
+static int check_perms(unsigned int perms, struct server_command *cmd_ptr)
+{
+ PARA_DEBUG_LOG("%s", "checking permissions\n");
+ return (cmd_ptr->perms & perms) < cmd_ptr->perms ? -E_PERM : 0;
+}
+
+/*
+ * Parse first string from *cmd and lookup in table of valid commands.
+ * On error, NULL is returned.
+ */
+static struct server_command *parse_cmd(const char *cmdstr)
+{
+ char buf[255];
+ int n = 0;
+
+ sscanf(cmdstr, "%200s%n", buf, &n);
+ if (!n)
+ return NULL;
+ buf[n] = '\0';
+ return get_cmd_ptr(buf, NULL);
+}
+
+long int para_rand(long unsigned max)
+{
+ return (long int) ((max + 0.0) * (random() / (RAND_MAX + 1.0)));
+}
+
+
+/* Open user_list file, returns pointer to opened file on success,
+ * NULL on errors
+ */
+static FILE *open_user_list(char *file)
+{
+ PARA_DEBUG_LOG("opening user list %s\n", file);
+ return fopen(file, "r");
+}
+
+/*
+ * lookup user in user_list file. Fills in a user struct containing
+ * filename of the user's public key as well as the permissions of that user.
+ * Returns 1 on success, 0 if user does not exist and < 0 on errors.
+ */
+static int get_user(struct user *user) {
+ FILE *file_ptr;
+ char *char_ptr;
+ char line[MAXLINE];
+ /* keyword, user, key, perms */
+ char w[MAXLINE], n[MAXLINE], k[MAXLINE], p[MAXLINE], tmp[4][MAXLINE];
+ int num;
+
+ file_ptr = open_user_list(user_list);
+ if (!file_ptr)
+ return -E_USERLIST;
+ while (fgets(line, MAXLINE, file_ptr)) {
+// PARA_DEBUG_LOG("%s: Read line (%i bytes) "
+// "from config file\n", __func__, strlen(line));
+ if (sscanf(line,"%200s %200s %200s %200s", w, n, k, p) < 3)
+ continue;
+ if (!strcmp(w, "user") && !strcmp(user->name, n)) {
+ PARA_DEBUG_LOG("found entry for %s\n", n);
+ strcpy(user->name, n);
+ strcpy(user->pubkey_file, k);
+ user->perms = 0;
+ char_ptr = p;
+ num = sscanf(char_ptr, "%200[A-Z_],%200[A-Z_],%200[A-Z_],%200[A-Z_]",
+ tmp[0], tmp[1], tmp[2], tmp[3]);
+ PARA_DEBUG_LOG("found %i perm entries\n",
+ num);
+ user->perms = 0;
+ while (num > 0) {
+ num--;
+ //PARA_DEBUG_LOG("%s: tmp[%i]=%s\n", __func__,
+ // num, tmp[num]);
+ if (!strcmp(tmp[num], "AFS_READ"))
+ user->perms =
+ user->perms | AFS_READ;
+ else if (!strcmp(tmp[num], "AFS_WRITE"))
+ user->perms =
+ user->perms | AFS_WRITE;
+ else if (!strcmp(tmp[num], "DB_READ"))
+ user->perms = user->perms | DB_READ;
+ else if (!strcmp(tmp[num], "DB_WRITE"))
+ user->perms = user->perms | DB_WRITE;
+ else /* unknown permission */
+ PARA_WARNING_LOG("unknown permission:"
+ "%s\n", tmp[num]);
+ }
+ fclose(file_ptr);
+ return 1;
+ }
+ }
+ fclose(file_ptr);
+ return 0;
+}
+
+static void init_rc4_keys(void)
+{
+ int i;
+
+ for (i = 0; i < 2 * RC4_KEY_LEN; i++)
+ rc4_buf[i] = para_rand(256);
+ PARA_DEBUG_LOG("rc4 keys initialized (%u:%u)\n",
+ (unsigned char) rc4_buf[0],
+ (unsigned char) rc4_buf[RC4_KEY_LEN]);
+ RC4_set_key(&rc4_recv_key, RC4_KEY_LEN, rc4_buf);
+ RC4_set_key(&rc4_send_key, RC4_KEY_LEN, rc4_buf + RC4_KEY_LEN);
+}
+
+static void rc4_recv(unsigned long len, const unsigned char *indata, unsigned char *outdata)
+{
+ RC4(&rc4_recv_key, len, indata, outdata);
+}
+static void rc4_send(unsigned long len, const unsigned char *indata, unsigned char *outdata)
+{
+ RC4(&rc4_send_key, len, indata, outdata);
+}
+
+
+
+
+int handle_connect(int fd, struct sockaddr_in *addr)
+{
+ int numbytes, ret, argc, use_rc4 = 0;
+ char buf[STRINGSIZE];
+ unsigned char crypt_buf[MAXLINE];
+ struct user u;
+ struct server_command *cmd = NULL;
+ long unsigned challenge_nr, chall_response;
+ char **argv = NULL;
+ char *p, *command = NULL;
+
+ signal(SIGCHLD, SIG_IGN);
+ signal(SIGINT, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+ signal(SIGHUP, SIG_DFL);
+ signal(SIGUSR1, SIG_IGN);
+
+ in_addr = addr;
+ challenge_nr = random();
+ /* send Welcome message */
+ ret = send_va_buffer(fd, "This is para_server, version " VERSION ".\n" );
+ if (ret < 0)
+ goto err_out;
+ /* recv auth request line */
+ ret = recv_buffer(fd, buf, sizeof(buf));
+ if (ret < 0)
+ goto err_out;
+ if (ret <= 6) {
+ ret = -E_AUTH;
+ goto err_out;
+ }
+ numbytes = ret;
+ ret = -E_AUTH;
+ if (strncmp(buf, "auth ", 5))
+ goto err_out;
+
+ if (numbytes < 9 || strncmp(buf, "auth rc4 ", 9))
+ strcpy(u.name, buf + 5); /* client version < 0.2.6 */
+ else {
+ strcpy(u.name, buf + 9); /* client version >= 0.2.6 */
+ use_rc4 = 1;
+ }
+// strcpy(u.name, buf + 5); /* ok, but ugly */
+ PARA_DEBUG_LOG("received %s request for user %s\n",
+ use_rc4? "rc4" : "auth", u.name);
+ /* lookup user in list file */
+ if ((ret = get_user(&u)) < 0)
+ goto err_out;
+ if (!ret) { /* user not found */
+ PARA_WARNING_LOG("auth request for unknown user %s\n", u.name);
+ ret = -E_BAD_USER;
+ goto err_out;
+ }
+ ret = para_encrypt_challenge(u.pubkey_file, challenge_nr, crypt_buf);
+ if (ret <= 0)
+ goto err_out;
+ numbytes = ret;
+ PARA_DEBUG_LOG("sending %d byte challenge\n", numbytes);
+ /* We can't use send_buffer here since buf may contain null bytes */
+ ret = send_bin_buffer(fd,(char *) crypt_buf, numbytes);
+ if (ret < 0)
+ goto err_out;
+ /* recv decrypted number */
+ numbytes = recv_buffer(fd, buf, sizeof(buf));
+ ret = numbytes;
+ if (ret < 0)
+ goto err_out;
+ ret = -E_AUTH;
+ if (!numbytes)
+ goto err_out;
+ if (sscanf(buf, CHALLENGE_RESPONSE_MSG "%lu", &chall_response) < 1
+ || chall_response != challenge_nr)
+ goto err_out;
+ /* auth successful. Send 'Proceed' message */
+ PARA_INFO_LOG("good auth for %s (%lu)\n", u.name, challenge_nr);
+ sprintf(buf, "%s", PROCEED_MSG);
+ if (use_rc4) {
+ init_rc4_keys();
+ ret = para_encrypt_buffer(u.pubkey_file, rc4_buf, 2 * RC4_KEY_LEN,
+ (unsigned char *)buf + PROCEED_MSG_LEN + 1);
+ if (ret <= 0)
+ goto err_out;
+ numbytes = ret + strlen(PROCEED_MSG) + 1;
+ } else
+ numbytes = strlen(buf);
+ ret = send_bin_buffer(fd, buf, numbytes);
+ if (ret < 0)
+ goto err_out;
+ if (use_rc4) {
+ crypt_function_recv = rc4_recv;
+ crypt_function_send = rc4_send;
+ PARA_INFO_LOG("%s", "rc4 encrytion activated\n");
+ }
+ /* read command */
+ while ((numbytes = recv_buffer(fd, buf, sizeof(buf))) > 0) {
+// PARA_INFO_LOG("recvd: %s (%d)\n", buf, numbytes);
+ ret = -E_COMMAND_SYNTAX;
+ if (command && numbytes + strlen(command) > STRINGSIZE) /* DOS */
+ goto err_out;
+ command = para_strcat(command, buf);
+ if ((p = strstr(command, EOC_MSG))) {
+ *p = '\0';
+ break;
+ }
+ }
+ ret = numbytes;
+ if (ret < 0)
+ goto err_out;
+ ret = -E_BAD_CMD;
+ /* parse command */
+ if (!(cmd = parse_cmd(command)))
+ goto err_out;
+ /* valid command, check permissions */
+ ret = check_perms(u.perms, cmd);
+ if (ret < 0)
+ goto err_out;
+ /* valid command and sufficient perms */
+ alarm(0);
+ argc = split_args(command, &argv, '\n');
+ argv[0] = cmd->name;
+ mmd_lock();
+ mmd->num_commands++;
+ mmd_unlock();
+ PARA_NOTICE_LOG("calling com_%s() for %s@%s\n", cmd->name, u.name,
+ inet_ntoa(addr->sin_addr));
+ ret = cmd->handler(fd, argc, argv);
+ if (ret >= 0) {
+ ret = EXIT_SUCCESS;
+ goto out;
+ }
+err_out:
+ if (ret != -E_SEND && ret != -E_RECV) {
+ PARA_NOTICE_LOG("%s\n", PARA_STRERROR(-ret));
+ send_va_buffer(fd, "%s\n", PARA_STRERROR(-ret));
+ }
+ ret = EXIT_FAILURE;
+out:
+ free(command);
+ free(argv);
+ mmd_lock();
+ if (cmd && (cmd->perms & DB_WRITE) && ret >= 0)
+ mmd->events++;
+ mmd->active_connections--;
+ mmd_unlock();
+ return ret;
+}
--- /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 compress.c paraslash's dynamic audio range compressor */
+
+/*
+ * Based on AudioCompress, (C) 2002-2004 M. Hari Nezumi <magenta@trikuare.cx>
+ */
+
+#include "gcc-compat.h"
+#include "para.h"
+#include "compress_filter.cmdline.h"
+#include "list.h"
+#include "filter.h"
+#include "string.h"
+
+/** how fine-grained the gain is */
+#define GAINSHIFT 10
+/** the size of the output data buffer */
+#define COMPRESS_CHUNK_SIZE 40960
+
+/** data specific to the compress filter */
+struct private_compress_data {
+ /** an array holding the previous peak values */
+ int *peaks;
+ /** current bucket number to be modified */
+ unsigned pn;
+ /** number of times clipping occured */
+ unsigned clip;
+ /** the current multiplier */
+ int current_gain;
+ /** the target multiplier */
+ int target_gain;
+ /** pointer to the configuration data for this instance of the compress filter */
+ struct gengetopt_args_info *conf;
+};
+
+static ssize_t compress(char *inbuf, size_t inbuf_len, struct filter_node *fn)
+{
+ int16_t *audio = (int16_t *) inbuf, *ip = audio, *op;
+ int peak = 1, pos = 0, i, gr, gf, gn;
+ size_t length = MIN((inbuf_len / 2) * 2, (fn->bufsize - fn->loaded) / 2 * 2);
+ struct private_compress_data *pcd = fn->private_data;
+
+ if (!length)
+ return 0;
+ /* determine peak's value and position */
+ for (i = 0; i < length / 2; i++, ip++) {
+ int val = ABS(*ip);
+ if (val > peak) {
+ peak = val;
+ pos = i;
+ }
+ }
+ pcd->peaks[pcd->pn] = peak;
+ for (i = 0; i < pcd->conf->buckets_arg; i++) {
+ if (pcd->peaks[i] > peak) {
+ peak = pcd->peaks[i];
+ pos = 0;
+ }
+ }
+ /* determine target gain */
+ gn = (1 << GAINSHIFT) * pcd->conf->target_level_arg / peak;
+ if (gn < (1 << GAINSHIFT))
+ gn = 1 << GAINSHIFT;
+ pcd->target_gain = (pcd->target_gain * ((1 << pcd->conf->gain_smooth_arg) - 1) + gn)
+ >> pcd->conf->gain_smooth_arg;
+ /* give it an extra insignificant nudge to counteract possible
+ * rounding error
+ */
+ if (gn < pcd->target_gain)
+ pcd->target_gain--;
+ else if (gn > pcd->target_gain)
+ pcd->target_gain++;
+ if (pcd->target_gain > pcd->conf->gain_max_arg << GAINSHIFT)
+ pcd->target_gain = pcd->conf->gain_max_arg << GAINSHIFT;
+ /* see if a peak is going to clip */
+ gn = (1 << GAINSHIFT) * 32768 / peak;
+ if (gn < pcd->target_gain) {
+ pcd->target_gain = gn;
+ if (pcd->conf->anticlip_given)
+ pos = 0;
+ } else
+ /* we're ramping up, so draw it out over the whole frame */
+ pos = length;
+ /* determine gain rate necessary to make target */
+ if (!pos)
+ pos = 1;
+ gr = ((pcd->target_gain - pcd->current_gain) << 16) / pos;
+ gf = pcd->current_gain << 16;
+ ip = audio;
+ op = (int16_t *)(fn->buf + fn->loaded);
+ for (i = 0; i < length / 2; i++) {
+ int sample;
+ /* interpolate the gain */
+ pcd->current_gain = gf >> 16;
+ if (i < pos)
+ gf += gr;
+ else if (i == pos)
+ gf = pcd->target_gain << 16;
+ /* amplify */
+ sample = (*ip++) * pcd->current_gain * pcd->conf->volume_arg / 10 >> GAINSHIFT;
+ if (sample < -32768) {
+ pcd->clip++;
+ sample = -32768;
+ } else if (sample > 32767) {
+ pcd->clip++;
+ sample = 32767;
+ }
+ *op++ = sample;
+ }
+ pcd->pn = (pcd->pn + 1) % pcd->conf->buckets_arg;
+ PARA_DEBUG_LOG("bucket: %03i, input len: %i, length: %i, peak: %05i, "
+ "current gain: %03i, clipped: %d\n", pcd->pn, inbuf_len,
+ length, peak, pcd->current_gain, pcd->clip);
+ fn->loaded = length;
+ return length;
+}
+
+static void close_compress(struct filter_node *fn)
+{
+ struct private_compress_data *pcd = fn->private_data;
+ free(pcd->peaks);
+ free(fn->private_data);
+ free(fn->buf);
+}
+
+static void *compress_parse_config(int argc, char **argv)
+{
+ struct gengetopt_args_info *ret = para_calloc(sizeof(struct gengetopt_args_info));
+ if (!compress_cmdline_parser(argc, argv, ret))
+ return ret;
+ free(ret);
+ return NULL;
+}
+
+static void open_compress(struct filter_node *fn)
+{
+ struct private_compress_data *pcd = para_calloc(
+ sizeof(struct private_compress_data));
+// compress_cmdline_parser(fn->argc, fn->argv, &pcd->conf);
+ pcd->conf = fn->conf;
+ pcd->peaks = para_calloc(pcd->conf->buckets_arg * sizeof(int));
+ fn->private_data = pcd;
+ fn->bufsize = COMPRESS_CHUNK_SIZE;
+ fn->buf = para_malloc(fn->bufsize);
+ fn->loaded = 0;
+}
+
+/** the init function of the compress filter */
+void compress_init(struct filter *f)
+{
+ f->open = open_compress;
+ f->close = close_compress;
+ f->convert = compress;
+ f->print_help = compress_cmdline_parser_print_help;
+ f->parse_config = compress_parse_config;
+}
--- /dev/null
+section "The dynamic audio range compressor"
+option "anticlip" c "enable clipping protection" flag on
+option "buckets" b "history length" int typestr="number" default="400" no
+option "target_level" t "target signal level (1-32767)" int typestr="number" default="25000" no
+option "gain_max" g "maximum amount to amplify by" int typestr="number" default="4" no
+option "gain_smooth" i "how much inertia ramping has" int typestr="number" default="5" no
+option "volume" v "set soft volume (0-10)" int typestr="number" default="10" no
--- /dev/null
+# -*- Autoconf -*-
+# Process this file with autoconf to produce a configure script.
+
+AC_PREREQ(2.53)
+
+
+AC_INIT(paraslash, [cvs], maan@systemlinux.org)
+AC_CONFIG_HEADER([config.h])
+
+########################################################################### generic
+AC_PROG_CC
+AC_PROG_CPP
+AC_PROG_INSTALL
+
+AC_HEADER_DIRENT
+AC_HEADER_STDC
+AC_HEADER_SYS_WAIT
+AC_CHECK_HEADERS([arpa/inet.h ctype.h fcntl.h limits.h netdb.h netinet/in.h stdlib.h \
+ string.h sys/socket.h sys/time.h sys/timeb.h sys/un.h sys/ipc.h unistd.h utime.h malloc.h], \
+ [], [AC_MSG_ERROR([$ac_header not found])])
+
+AC_CHECK_HEADER(linux/soundcard.h, [extras="$extras para_fade"],
+ [AC_MSG_WARN([linux/soundcard.h not found, \
+ cannot build para_fade])])
+
+# Checks for typedefs, structures, and compiler characteristics.
+AC_C_CONST
+AC_TYPE_OFF_T
+AC_TYPE_PID_T
+AC_TYPE_SIZE_T
+AC_HEADER_TIME
+AC_STRUCT_TM
+
+# Checks for library functions.
+AC_FUNC_FORK
+AC_PROG_GCC_TRADITIONAL
+AC_FUNC_MALLOC
+AC_FUNC_MEMCMP
+AC_FUNC_MKTIME
+AC_FUNC_MMAP
+AC_FUNC_REALLOC
+AC_FUNC_SELECT_ARGTYPES
+AC_TYPE_SIGNAL
+AC_FUNC_STAT
+AC_FUNC_STRFTIME
+AC_FUNC_VPRINTF
+AC_CHECK_FUNCS([atexit dup2 gethostbyname inet_ntoa memchr memmove memset \
+ regcomp select socket strchr strdup strerror strstr strtol uname], [],
+ [AC_MSG_ERROR([function not found, cannot live without it])])
+########################################################################### curses
+AC_CHECK_LIB([ncurses], [initscr], [],
+ [AC_MSG_ERROR([libncurses not found])])
+
+AC_CHECK_LIB([readline], [readline], [],
+ [AC_MSG_ERROR([libreadline not found])])
+AC_CHECK_LIB([menu], [new_menu], [extras="$extras para_dbadm"],
+ [AC_MSG_WARN([libmenu not found, cannot build para_dbadm])])
+
+###########################################################################
+recv_cmdline_objs="recv.cmdline http_recv.cmdline"
+recv_errlist_objs="http_recv recv_common recv time string net"
+recv_ldflags=""
+
+filter_cmdline_objs="filter.cmdline compress_filter.cmdline"
+filter_errlist_objs="filter_chain wav compress filter string"
+filter_ldflags=""
+
+audiod_cmdline_objs="audiod.cmdline grab_client.cmdline compress_filter.cmdline
+ http_recv.cmdline"
+audiod_errlist_objs="audiod exec close_on_fork signal string daemon stat net
+ time grab_client filter_chain wav compress http_recv recv_common ringbuffer"
+audiod_ldflags=""
+
+server_cmdline_objs="server.cmdline"
+server_errlist_objs="server mp3 afs command net string signal dopey time daemon stat
+ crypt http_send db close_on_fork"
+server_ldflags=""
+
+########################################################################### ssl
+dnl @synopsis CHECK_SSL
+dnl
+dnl based on the follwing macro from the autoconf archive
+dnl
+dnl @category InstalledPackages
+dnl @author Mark Ethan Trostler <trostler@juniper.net>
+dnl @version 2003-01-28
+dnl @license AllPermissive
+
+AC_DEFUN([CHECK_SSL],
+[
+ for ssldir in $1 /usr/local/ssl /usr/lib/ssl /usr/ssl /usr/pkg /usr/local /usr; do
+ AC_MSG_CHECKING(for openssl in $ssldir)
+ if test -f "$ssldir/include/openssl/ssl.h"; then
+ found_ssl="yes"
+ AC_MSG_RESULT(yes)
+ SSL_CFLAGS="-I$ssldir/include/openssl"
+ SSL_CPPFLAGS="-I$ssldir/include/openssl"
+ break
+ fi
+ if test -f "$ssldir/include/ssl.h"; then
+ found_ssl="yes";
+ AC_MSG_RESULT(yes)
+ SSL_CFLAGS="-I$ssldir/include/";
+ SSL_CPPFLAGS="-I$ssldir/include/";
+ break
+ fi
+ AC_MSG_RESULT(no)
+ done
+ if test x_$found_ssl != x_yes; then
+ AC_MSG_ERROR(Cannot find ssl libraries)
+ else
+ SSL_LIBS="-lssl -lcrypto";
+ SSL_LDFLAGS="-L$ssldir/lib";
+ fi
+ AC_SUBST(SSL_CPPFLAGS)
+ AC_SUBST(SSL_CFLAGS)
+ AC_SUBST(SSL_LIBS)
+ AC_SUBST(SSL_LDFLAGS)
+])dnl
+
+AC_ARG_ENABLE(ssldir, [AS_HELP_STRING(--enable-ssldir=path,
+ [Search for openssl also in path.])])
+if test "$enable_ssldir" = "yes"; then enable_ssldir=""; fi
+CHECK_SSL($enable_ssldir)
+server_ldflags="$srver_ldflags $SSL_LDFLAGS $SSL_LIBS"
+
+########################################################################### gtk2
+
+pkg_modules="gtk+-2.0 >= 2.0.0"
+PKG_CHECK_MODULES(GTK, [$pkg_modules], [extras="$extras para_krell.so"], [
+ AC_MSG_WARN([gtk+-2 not found, can not build para_krell])
+])
+AC_SUBST(GTK_CFLAGS)
+AC_SUBST(GTK_LIBS)
+
+########################################################################### sdl
+AC_CHECK_LIB([SDL_image], [SDL_Init], [extras="$extras para_sdl_gui"], [
+ AC_MSG_WARN([libSDL_image not found, cannot build para_sdl_gui])
+])
+AC_CHECK_HEADER(SDL/SDL.h, [],
+ [AC_MSG_WARN([SDL/SDL.h not found])])
+
+########################################################################### mysql
+have_mysql="yes"
+AC_CHECK_HEADER(mysql/mysql.h, [], [
+ have_mysql="no"
+])
+AC_CHECK_LIB([mysqlclient], [mysql_init], [], [
+ have_mysql="no"
+])
+if test "$have_mysql" = "yes"; then
+ server_ldflags="$server_ldflags -lmysqlclient"
+ server_errlist_objs="$server_errlist_objs mysql"
+ AC_DEFINE(HAVE_MYSQL, 1, [define to 1 to turn on mysql support])
+else
+ AC_MSG_WARN([no libmysqlclient, cannot build mysql-based dbtool])
+fi
+########################################################################### ogg
+have_ogg="yes"
+AC_CHECK_LIB([ogg], [ogg_stream_init], [], [
+ have_ogg="no"
+])
+AC_CHECK_LIB([vorbis], [vorbis_info_init], [], [
+ have_ogg="no"
+])
+AC_CHECK_HEADERS([ogg/ogg.h vorbis/codec.h], [], [
+ have_ogg="no"
+])
+if test "$have_ogg" = "yes"; then
+ AC_DEFINE(HAVE_OGGVORBIS, 1, define to 1 to turn on ogg vorbis support)
+ server_ldflags="$server_ldflags -logg -lvorbis -lvorbisfile"
+ filter_ldflags="$filter_ldflags -lvorbis -lvorbisfile"
+ audiod_ldflags="$audiod_ldflags -lvorbis -lvorbisfile"
+
+ filter_cmdline_objs="$filter_cmdline_objs oggdec_filter.cmdline"
+ audiod_cmdline_objs="$audiod_cmdline_objs oggdec_filter.cmdline"
+
+ server_errlist_objs="$server_errlist_objs ogg"
+ filter_errlist_objs="$filter_errlist_objs oggdec"
+ audiod_errlist_objs="$audiod_errlist_objs oggdec"
+else
+ AC_MSG_WARN([no ogg vorbis support in para_server/para_filter])
+fi
+########################################################################### mad
+have_mad="yes"
+AC_CHECK_HEADERS([mad.h], [], [
+ have_mad="no"
+])
+AC_CHECK_LIB([mad], [mad_stream_init], [], [
+ have_mad="no"
+])
+if test "$have_mad" = "yes"; then
+ AC_DEFINE(HAVE_MAD, 1, define to 1 if you want to build the mp3dec filter)
+ filter_errlist_objs="$filter_errlist_objs mp3dec"
+ audiod_errlist_objs="$audiod_errlist_objs mp3dec"
+ filter_ldflags="$filter_ldflags -lmad"
+ audiod_ldflags="$audiod_ldflags -lmad"
+else
+ AC_MSG_WARN([no mp3dec support in para_audiod/para_filter])
+fi
+########################################################################### alsa
+play="para_play"
+msg="will not build para_play"
+AC_CHECK_HEADERS([alsa/asoundlib.h], [], [
+ AC_MSG_WARN([no alsa/asoundlib, $msg])
+ play=""
+])
+AC_CHECK_LIB([asound], [snd_pcm_open], [], [
+ AC_MSG_WARN([no libasound, $msg])
+ play=""
+])
+extras="$extras $play"
+
+
+########################################################################### ortp
+have_ortp="yes"
+pkg_modules="glib-2.0 >= 2.0.4"
+PKG_CHECK_MODULES(GLIB, [$pkg_modules], [], [
+ have_ortp="no"
+])
+CPPFLAGS="$CPPFLAGS $GLIB_CFLAGS"
+AC_CHECK_HEADERS([ortp/ortp.h], [], [
+ have_ortp="no"
+])
+AC_CHECK_LIB([ortp], [ortp_init], [], [
+ have_ortp="no"
+])
+if test "$have_ortp" = "yes"; then
+ recv_cmdline_objs="$recv_cmdline_objs ortp_recv.cmdline"
+ recv_errlist_objs="$recv_errlist_objs ortp_recv"
+
+ audiod_cmdline_objs="$audiod_cmdline_objs ortp_recv.cmdline"
+ audiod_errlist_objs="$audiod_errlist_objs ortp_recv"
+
+ server_errlist_objs="$server_errlist_objs ortp_send"
+
+ recv_ldflags="$recv_ldflags $GLIB_LIBS -lortp"
+ server_ldflags="$server_ldflags $GLIB_LIBS -lortp"
+ audiod_ldflags="$audiod_ldflags $GLIB_LIBS -lortp"
+ AC_DEFINE(HAVE_ORTP, 1, [define to 1 to turn on ortp support])
+
+else
+ AC_MSG_WARN([deactivating ortp support])
+fi
+AC_SUBST(GLIB_CFLAGS)
+AC_SUBST(GLIB_LIBS)
+
+########################################################################### zmw
+slide="para_slider"
+msg="can not build para_slider"
+CPPFLAGS="$GTK_CFLAGS"
+LDFLAGS="$LDFLAGS $GTK_LIBS"
+AC_CHECK_HEADERS([zmw/zmw.h], [], [
+ AC_MSG_WARN([zero memory widget header files not found, $msg])
+ slide=""
+])
+AC_CHECK_LIB([zmw], [zmw_init], [], [
+ AC_MSG_WARN([zero memory widget library not found, $msg])
+ slide=""
+])
+extras="$extras $slide"
+
+
+
+
+
+AC_SUBST(extra_binaries, [$extras])
+AC_SUBST(extra_filter_objs, [$extra_filter_objs])
+AC_SUBST(extra_filter_libs, [$extra_filter_libs])
+AC_SUBST(install_sh, [$INSTALL])
+AC_CONFIG_FILES([Makefile])
+
+
+
+AC_DEFUN([add_dot_o],[$(for i in $@; do printf "$i.o "; done)])
+AC_DEFUN([objlist_to_errlist],[$(for i in $@; do printf "DEFINE_ERRLIST($(echo $i| tr 'a-z' 'A-Z'));"; done) [const char **para_errlist[[]]] = {$(for i in $@; do printf "PARA_ERRLIST($(echo $i | tr 'a-z' 'A-Z')), "; done) }])
+
+recv_objs="$recv_cmdline_objs $recv_errlist_objs"
+filter_objs="$filter_cmdline_objs $filter_errlist_objs"
+audiod_objs="$audiod_cmdline_objs $audiod_errlist_objs"
+server_objs="$server_cmdline_objs $server_errlist_objs"
+
+AC_SUBST(recv_objs, add_dot_o($recv_objs))
+AC_SUBST(recv_ldflags, $recv_ldflags)
+AC_DEFINE_UNQUOTED(INIT_RECV_ERRLISTS, objlist_to_errlist($recv_errlist_objs),
+ errors used by para_recv)
+
+AC_SUBST(filter_objs, add_dot_o($filter_objs))
+AC_SUBST(filter_ldflags, $filter_ldflags)
+AC_DEFINE_UNQUOTED(INIT_FILTER_ERRLISTS,
+ objlist_to_errlist($filter_errlist_objs), errors used by para_filter)
+
+AC_SUBST(audiod_objs, add_dot_o($audiod_objs))
+AC_SUBST(audiod_ldflags, $audiod_ldflags)
+AC_DEFINE_UNQUOTED(INIT_AUDIOD_ERRLISTS, objlist_to_errlist($audiod_errlist_objs),
+ errors used by para_audiod)
+
+AC_SUBST(server_objs, add_dot_o($server_objs))
+AC_SUBST(server_ldflags, $server_ldflags)
+AC_DEFINE_UNQUOTED(INIT_SERVER_ERRLISTS,
+ objlist_to_errlist($server_errlist_objs), errors used by para_server)
+AC_OUTPUT
+AC_MSG_NOTICE([creating Makefile.deps])
+gcc -MM -MG *.c > Makefile.deps
+AC_MSG_NOTICE([
+paraslash configuration:
+~~~~~~~~~~~~~~~~~~~~~~~~
+mysql support: $have_mysql
+ogg vorbis support: $have_ogg
+mp3dec support (libmad): $have_mad
+ortp support: $have_ortp
+])
--- /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 crypt.c openssl-based RSA encryption/decryption routines */
+
+#include <openssl/pem.h>
+#include "para.h"
+#include "error.h"
+#include "string.h"
+
+/** \cond used to distinguish between loading of private/public key */
+#define LOAD_PUBLIC_KEY 0
+#define LOAD_PRIVATE_KEY 1
+/** \endcond **/
+
+static EVP_PKEY *load_key(const char *file, int private)
+{
+ BIO *key;
+ EVP_PKEY *pkey = NULL;
+
+ key = BIO_new(BIO_s_file());
+ if (!key)
+ return NULL;
+ if (BIO_read_filename(key, file) > 0) {
+ if (private == LOAD_PRIVATE_KEY)
+ pkey = PEM_read_bio_PrivateKey(key, NULL, NULL, NULL);
+ else
+ pkey = PEM_read_bio_PUBKEY(key, NULL, NULL, NULL);
+ }
+ BIO_free(key);
+ return pkey;
+}
+
+static int get_key(char *key_file, RSA **rsa, int private)
+{
+ EVP_PKEY *key = load_key(key_file, private);
+
+ if (!key)
+ return (private == LOAD_PRIVATE_KEY)? -E_PRIVATE_KEY : -E_PUBLIC_KEY;
+ *rsa = EVP_PKEY_get1_RSA(key);
+ EVP_PKEY_free(key);
+ if (!*rsa)
+ return -E_RSA;
+ return RSA_size(*rsa);
+}
+
+/**
+ * decrypt a buffer using an RSA key
+ *
+ * \param key_file full path of the rsa key
+ * \param outbuf the output buffer
+ * \param inbuf the encrypted input buffer
+ * \param rsa_inlen the length of \a inbuf
+ *
+ * The \a outbuf must be large enough to hold at least \a rsa_inlen bytes.
+ *
+ * \return The size of the recovered plaintext on success, negative on errors.
+ *
+ * \sa RSA_private_decrypt(3)
+ **/
+int para_decrypt_buffer(char *key_file, unsigned char *outbuf, unsigned char *inbuf,
+ int rsa_inlen)
+{
+ RSA *rsa;
+ int ret = get_key(key_file, &rsa, LOAD_PRIVATE_KEY);
+
+ if (ret < 0)
+ return ret;
+ ret = RSA_private_decrypt(rsa_inlen, inbuf, outbuf, rsa, RSA_PKCS1_PADDING);
+ return (ret > 0)? ret : -E_DECRYPT;
+}
+
+/**
+ * decrypt the challenge number sent by para_server
+ *
+ * \param key_file full path of the rsa key
+ * \param challenge_nr result is stored here
+ * \param inbuf the input buffer
+ * \param rsa_inlen the length of \a inbuf
+ *
+ * \return positive on success, negative on errors
+ *
+ * \sa para_decrypt_buffer()
+ */
+int para_decrypt_challenge(char *key_file, long unsigned *challenge_nr,
+ unsigned char *inbuf, int rsa_inlen)
+{
+ unsigned char *rsa_out = OPENSSL_malloc(128);
+ int ret = para_decrypt_buffer(key_file, rsa_out, inbuf, rsa_inlen);
+
+ if (ret >= 0)
+ ret = sscanf((char *)rsa_out, "%lu", challenge_nr) == 1?
+ 1 : -E_CHALLENGE;
+ OPENSSL_free(rsa_out);
+ return ret;
+}
+
+/**
+ * encrypt a buffer using an RSA key
+ *
+ * \param key_file full path of the rsa key
+ * \param inbuf the input buffer
+ * \param len the length of \a inbuf
+ * \param outbuf the output buffer
+ *
+ * \return The size of the encrypted data on success, negative on errors
+ *
+ * \sa RSA_public_encrypt(3)
+ */
+int para_encrypt_buffer(char *key_file, unsigned char *inbuf,
+ const unsigned len, unsigned char *outbuf)
+{
+ RSA *rsa;
+ int ret = get_key(key_file, &rsa, LOAD_PUBLIC_KEY);
+
+ if (ret < 0)
+ return ret;
+ ret = RSA_public_encrypt(len, inbuf, outbuf, rsa, RSA_PKCS1_PADDING);
+ return ret < 0? -E_ENCRYPT : ret;
+}
+
+/**
+ * encrypt the given challenge number
+ *
+ * \param key_file full path of the rsa key
+ * \param challenge_nr the number to be encrypted
+ * \param outbuf the output buffer
+ *
+ * \a outbuf must be at least 64 bytes long
+ *
+ * \return The size of the encrypted data on success, negative on errors
+ *
+ * \sa para_encrypt_buffer()
+ *
+ */
+int para_encrypt_challenge(char *key_file, long unsigned challenge_nr,
+ unsigned char *outbuf)
+{
+ unsigned char *inbuf = (unsigned char*) make_message("%lu", challenge_nr);
+ int ret = para_encrypt_buffer(key_file, inbuf, strlen((char *)inbuf), outbuf);
+ free(inbuf);
+ return ret;
+}
+
--- /dev/null
+/** \file crypt.h prototypes for the RSA crypt functions */
+int para_decrypt_challenge(char *key_file, long unsigned *challenge_nr,
+ unsigned char *buf, int rsa_inlen);
+int para_encrypt_challenge(char *key_file, long unsigned challenge_nr,
+ unsigned char *outbuf);
+int para_encrypt_buffer(char *key_file, unsigned char *inbuf, const unsigned len,
+ unsigned char *outbuf);
+int para_decrypt_buffer(char *key_file, unsigned char *outbuf, unsigned char *inbuf,
+ int rsa_inlen);
--- /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 daemon.c some helpers for programs that detach from the console */
+#include "para.h"
+#include "daemon.h"
+#include <pwd.h>
+#include "string.h"
+
+/**
+ * do the usual stuff to become a daemon
+ *
+ * Fork, become session leader, dup fd 0, 1, 2 to /dev/null.
+ *
+ * \sa fork(2), setsid(2), dup(2)
+ */
+void daemon_init(void)
+{
+ pid_t pid;
+ int null;
+
+ PARA_INFO_LOG("%s", "daemonizing\n");
+ if ((pid = fork()) < 0) {
+ PARA_EMERG_LOG("%s", "failed to init daemon\n");
+ exit(EXIT_FAILURE);
+ } else if (pid)
+ exit(EXIT_SUCCESS); /* parent exits */
+ /* child */
+ setsid(); /* become session leader */
+ chdir("/");
+ umask(0);
+
+ null = open("/dev/null", O_RDONLY);
+ if (null < 0)
+ exit(EXIT_FAILURE);
+ dup2(null, STDIN_FILENO);
+ dup2(null, STDOUT_FILENO);
+ dup2(null, STDERR_FILENO);
+ close(null);
+}
+
+/**
+ * fopen() a file in append mode
+ *
+ * \param logfile_name the name of the file to open
+ *
+ * Either calls exit() or returns a valid file handle.
+ */
+/* might be called from para_log, so we must not use para_log */
+FILE *open_log(const char *logfile_name)
+{
+ FILE *logfile;
+
+ if (!logfile_name)
+ return NULL;
+ if (!(logfile = fopen(logfile_name, "a")))
+ exit(EXIT_FAILURE);
+ setlinebuf(logfile);
+ return logfile;
+}
+
+/**
+ * close the log file of the daemon
+ *
+ * \param logfile the log file handle
+ *
+ * It's OK to call this with logfile == NULL
+ */
+void close_log(FILE* logfile)
+{
+ if (!logfile)
+ return;
+ PARA_INFO_LOG("%s", "closing logfile\n");
+ fclose(logfile);
+}
+
+/**
+ * log the startup message containing the paraslash version
+ */
+void log_welcome(const char *whoami, int loglevel)
+{
+ PARA_INFO_LOG("welcome to %s " VERSION " ("BUILD_DATE")\n", whoami);
+ PARA_DEBUG_LOG("using loglevel %d\n", loglevel);
+}
+
+/**
+ * give up superuser privileges
+ *
+ * This function returns immediately if not invoked with EUID zero. Otherwise,
+ * it tries to obtain the UID for the user specified by \a username and exits
+ * if this user was not found. On success, effective and real UID and the saved
+ * set-user-ID are all set to the UID of \a username.
+ *
+ * \sa getpwnam(3), getuid(2), setuid(2)
+ */
+void para_drop_privileges(const char *username)
+{
+ struct passwd *p;
+ char *tmp;
+
+ if (geteuid())
+ return;
+ if (!username) {
+ PARA_EMERG_LOG("%s", "root privileges, but no user option given\n");
+ exit(EXIT_FAILURE);
+ }
+ tmp = para_strdup(username);
+ p = getpwnam(tmp);
+ free(tmp);
+ if (!p) {
+ PARA_EMERG_LOG("%s", "no such user\n");
+ exit(EXIT_FAILURE);
+ }
+ PARA_NOTICE_LOG("%s", "dropping root privileges\n");
+ setuid(p->pw_uid);
+ PARA_DEBUG_LOG("uid: %d, euid: %d\n", getuid(), geteuid());
+ setuid(p->pw_uid);
+}
+
+/**
+ * set/get the server uptime
+ *
+ * \param set_or_get chose one of the two modes
+ *
+ * This should be called at startup time with \a set_or_get equal to \p
+ * UPTIME_SET which sets the uptime to zero. Subsequent calls with \a
+ * set_or_get equal to \p UPTIME_GET return the number of seconds ellapsed
+ * since the last reset.
+ *
+ * \sa time(2), difftime(3)
+ */
+time_t server_uptime(enum uptime set_or_get)
+{
+ static time_t startuptime;
+ time_t now;
+
+ if (set_or_get == UPTIME_SET) {
+ time(&startuptime);
+ return 0;
+ }
+ time(&now);
+ return (time_t) difftime(now, startuptime);
+}
+
+/**
+ * construct string containing uptime
+ *
+ * The format of the returned string is "days:hours:minutes"
+ *
+ * \sa server_uptime
+ */
+__malloc char *uptime_str(void)
+{
+ time_t t = server_uptime(UPTIME_GET);
+ return make_message("%li:%02li:%02li", t / 86400,
+ (t / 3600) % 24, (t / 60) % 60);
+}
+
--- /dev/null
+/** \file daemon.h exported symbols from daemon.c */
+
+
+void daemon_init(void);
+FILE *open_log(const char *logfile_name);
+void close_log(FILE* logfile);
+void log_welcome(const char *whoami, int loglevel);
+void para_drop_privileges(const char *username);
+/** used for server_uptime() */
+enum uptime {UPTIME_SET, UPTIME_GET};
+time_t server_uptime(enum uptime set_or_get);
+__malloc char *uptime_str(void);
--- /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 db.c functions common to all database tools. */
+
+#include "server.cmdline.h"
+#include "server.h"
+#include "afs.h"
+#include <dirent.h> /* readdir() */
+#include <sys/stat.h> /* stat */
+#include <sys/types.h> /* mode_t */
+#include "error.h"
+#include "string.h"
+
+/*
+ * return 1 if name matches any supported audio format
+ */
+static int match_audio_file_name(char *name)
+{
+ int i, len = strlen(name);
+ const char *pattern[] = {SUPPORTED_AUDIO_FORMATS_ARRAY};
+
+ for (i = 0; pattern[i]; i++) {
+ const char *p = pattern[i];
+ int plen = strlen(p);
+ if (len < plen + 1)
+ continue;
+ if (name[len - plen - 1] != '.')
+ continue;
+ if (strcasecmp(name + len - plen, p))
+ continue;
+ return 1;
+ }
+ return 0;
+}
+
+/**
+ * traverse the given directory recursively
+ *
+ * @param dirname the directory to traverse
+ * @param f: the function to call for each entry.
+ *
+ * for each regular file whose filename ends in .yyy, where yyy is a supported
+ * audio format, the supplied function \a f is called. The directory and
+ * filename component of the regular file are passed to \a f.
+ *
+ * \return On success, 1 is returned. Otherwise, this function returns a
+ * negative value which indicates the kind of the error.
+ */
+int find_audio_files(const char *dirname, int (*f)(const char *, const char *))
+{
+ DIR *dir = NULL;
+ struct dirent *entry;
+ /*
+ * Opening the current directory (".") and calling fchdir() to return
+ * is usually faster and more reliable than saving cwd in some buffer
+ * and calling chdir() afterwards (see man 3 getcwd).
+ */
+ char cwd_fd = open(".", O_RDONLY);
+ struct stat s;
+ int ret = -1;
+
+// PARA_DEBUG_LOG("dirname: %s\n", dirname);
+ if (cwd_fd < 0)
+ return -E_GETCWD;
+ ret = -E_CHDIR;
+ if (chdir(dirname) < 0)
+ goto out;
+ ret = -E_OPENDIR;
+ dir = opendir(".");
+ if (!dir)
+ goto out;
+ /* scan cwd recursively */
+ while ((entry = readdir(dir))) {
+ mode_t m;
+ char *tmp;
+
+ if (!strcmp(entry->d_name, "."))
+ continue;
+ if (!strcmp(entry->d_name, ".."))
+ continue;
+ ret = -E_LSTAT;
+ if (lstat(entry->d_name, &s) == -1)
+ goto out;
+ m = s.st_mode;
+ if (!S_ISREG(m) && !S_ISDIR(m)) /* skip links, sockets, ... */
+ continue;
+ if (S_ISREG(m)) { /* regular file */
+ if (!match_audio_file_name(entry->d_name))
+ continue;
+ if (f(dirname, entry->d_name) < 0)
+ goto out;
+ continue;
+ }
+ /* directory */
+ tmp = make_message("%s/%s", dirname, entry->d_name);
+ ret = find_audio_files(tmp, f);
+ free(tmp);
+ if (ret < 0)
+ goto out;
+ }
+ ret = 1;
+out:
+ if (dir)
+ closedir(dir);
+ if (fchdir(cwd_fd) < 0)
+ ret = -E_CHDIR;
+ close(cwd_fd);
+ if (ret < 0)
+ PARA_ERROR_LOG("ret = %x\n", -ret);
+ return ret;
+}
--- /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 db.h data structures common to all database tools */
+
+int find_audio_files(const char *dirname, int (*f)(const char *, const char *));
+
+/**
+ * describes one of para_server's supported database tools
+ *
+ * There is exactly one such struct for each supported database tool. During
+ * the startup part of para_server the \a init() function of the activated
+ * database tool gets called which fills in the other members.
+
+ *
+ *
+ */
+struct dbtool {
+/**
+ * name name of this database tool
+ */
+const char *name;
+/**
+ * the database init routine
+ *
+ * It should check its command line options and do all necessary initialization
+ * like connecting to a database server.
+ *
+ * A negative return value indicates an initialization error and means that
+ * this database tool should be ignored for now (it may later be activated
+ * again via the cdt command).
+ *
+ * If \a init() returns success (non-negative return value), it must have
+ * initialized in all non-optional function pointers of the given dbtool
+ * struct. Moreover, \a cmd_list must point to a NULL-terminated array which
+ * holds the list of all commands that are supported by this database tool.
+ */
+int (*init)(struct dbtool *self);
+/**
+ * list of commands supported by this dbtool
+ */
+struct server_command *cmd_list;
+/**
+ * pointer to function returning list of at most \a num audio files to be
+ * streamed next
+ *
+ * \a get_audio_file_list() must return a pointer to a array of at most \a num
+ * char* pointers (terminated by a NULL pointer), or NULL on errors. Both the
+ * array and its contents must be dynamically allocated and are freed by the
+ * caller.
+ *
+*/
+char **(*get_audio_file_list)(unsigned int num);
+/**
+ *
+ * the update hook
+ *
+ * The \a update_audio_file pointer is optional and need not be supplied. In this
+ * case it is not neccessary to init this pointer from within init(). If
+ * \a update_audio_file is non-NULL, the function it points to gets called
+ * whenever a new audio file was successfully loaded and is going to be
+ * streamed by any of paraslash's senders. The full path of the audio file is
+ * passed \a update_audio_file().
+ *
+ */
+void (*update_audio_file)(char *audio_file);
+/**
+ *
+ * shutdown this database tool and free all resources
+ *
+ * This gets called whenever the database tool changes (via the cdt command),
+ * or when para_server receives the HUP signal, or when para_server shuts down.
+ * It is assumed to succeed.
+*/
+void (*shutdown)(void);
+};
+
+int mysql_dbtool_init(struct dbtool*);
+int dopey_dbtool_init(struct dbtool*);
+
--- /dev/null
+/*
+ * Copyright (C) 2004-2006 Andre Noll <maan@systemlinux.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+ */
+
+/** \file dbadm.c simple attribute setting utility for the mysql dbtool */
+
+#include "para.h"
+#include <menu.h>
+#include "string.h"
+
+//#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
+#define MAXLINE 255
+
+#define OFFSET 4
+static int att_win_lines, att_win_cols;
+static int att_format_lines, att_format_cols;
+static int atts_modified, refresh_file = 1;
+static int n_choices, choice_len;
+static char *atts;
+ITEM **my_items;
+WINDOW *att_win;
+
+static char **choices;
+
+/* no looging */
+void para_log(__unused int ll, __unused char *fmt,...)
+{
+}
+
+static int client_cmd(const char *cmd)
+{
+ pid_t pid;
+ int ret, fds[3] = {0, 1, 0};
+ char *cmdline = make_message(BINDIR "/para_client %s", cmd);
+ ret = para_exec_cmdline_pid(&pid, cmdline, fds);
+ free(cmdline);
+ if (ret < 0)
+ return -1;
+ return fds[1];
+}
+
+static char **get_all_atts(int *num_atts)
+{
+ int fd = client_cmd("laa");
+ FILE *pipe;
+ char **ret = NULL, *buf;
+
+ if (fd < 0)
+ return NULL;
+ pipe = fdopen(fd, "r");
+ if (!pipe) {
+ close(fd);
+ return NULL;
+ }
+ *num_atts = 0;
+ buf = para_malloc(MAXLINE * sizeof(char));
+ while (fgets(buf, MAXLINE - 1, pipe) && *buf) {
+ size_t n = strlen(buf);
+ buf[n - 1] = '\0';
+ if (choice_len < n - 1)
+ choice_len = n - 1;
+ ret = para_realloc(ret, (*num_atts + 1) * sizeof(char*));
+ ret[*num_atts] = para_strdup(buf);
+ *num_atts += 1;
+ }
+ free(buf);
+ return ret;
+}
+
+static char *get_atts(char *filename)
+{
+ int n, fd, bufsize = (n_choices * (choice_len + 1) + 10) * sizeof(char);
+ char *cmd = make_message("la %s", filename), *buf;
+
+ fd = client_cmd(cmd);
+ free(cmd);
+ if (fd < 0)
+ return NULL;
+ buf = para_malloc(bufsize * sizeof(char));
+ n = read(fd, buf, bufsize - 1);
+ if (n <= 0 ||strstr(buf, "Not contained in database")) {
+ free(buf);
+ return NULL;
+ };
+ return buf;
+}
+
+static void _item_init(__unused MENU* menu)
+{
+// static int subsequent_run;
+ int i, n;
+ char *p = atts;
+ char att[MAXLINE];
+
+ if (!refresh_file)
+ return;
+ refresh_file = 0;
+ for (i = 0; i < n_choices; i++)
+ set_item_value(my_items[i], FALSE);
+ while (sscanf(p, "%s%n", att, &n) > 0) {
+ //mvprintw(LINES - 4, 0, "aaaitem.");
+ p += n + 1;
+ for (i = 0; i < n_choices; i++) {
+ if (!strcmp(item_name(my_items[i]), att)) {
+ set_item_value(my_items[i], TRUE);
+ break;
+ }
+ }
+ }
+}
+
+struct color_pair {
+ int bg;
+ int fg;
+};
+
+enum {
+ COLOR_DUMMY,
+ COLOR_FRAME,
+ COLOR_FILENAME,
+ COLOR_ACTIVE_ITEM,
+ COLOR_INACTIVE_ITEM
+};
+
+static void init_colors(void)
+{
+ start_color();
+ init_pair(COLOR_FRAME, COLOR_BLUE, COLOR_BLACK);
+ init_pair(COLOR_FILENAME, COLOR_MAGENTA, COLOR_BLACK);
+ init_pair(COLOR_ACTIVE_ITEM, COLOR_RED, COLOR_WHITE);
+ init_pair(COLOR_INACTIVE_ITEM, COLOR_WHITE, COLOR_BLACK);
+}
+
+static int commit_changes(char *filename)
+{
+// ITEM **items;
+ int i;
+ char buf[MAXLINE] = "para_client sa ";
+
+ for (i = 0; i < n_choices; ++i) {
+ strcat(buf, item_name(my_items[i]));
+ if (item_value(my_items[i])) {
+ // printf("%s\n", item_name(my_items[i]));
+ strcat(buf, "+ ");
+ } else
+ strcat(buf, "- ");
+ }
+ strcat(buf, filename);
+ //printf("old atts: %s\n", atts);
+ //printf("%s\n", buf);
+ return system(buf);
+}
+
+static char *get_current_filename(void)
+{
+ char *bn = NULL, *buf = para_malloc(MAXLINE * sizeof(char));
+ int ret, fd;
+
+ fd = client_cmd("sc 1");
+ if (fd < 0)
+ return NULL;
+ ret = read(fd, buf, MAXLINE - 1);
+ if (ret <= 0)
+ goto out;
+ buf[ret] = '\0';
+ bn = para_basename(buf);
+ free(buf);
+out:
+ close(fd);
+ return bn;
+}
+
+static void print_filename(char *filename)
+{
+ char *tmp = strdup(filename);
+ int maxlen = att_win_cols - 2;
+
+ wattron(att_win, COLOR_PAIR(COLOR_FILENAME));
+ if (strlen(filename) > maxlen)
+ tmp[maxlen] = '\0';
+ wmove(att_win, 1, 1);
+ clrtoeol();
+ mvwprintw(att_win, 1, 1, "%s", tmp);
+ wattron(att_win, COLOR_PAIR(COLOR_FRAME));
+ mvwaddch(att_win, 2, att_win_cols - 1, ACS_RTEE);
+ free(tmp);
+}
+
+static int com_refresh_file(char *filename) {
+
+
+ filename = get_current_filename();
+ if (!filename)
+ return -1;
+ atts = get_atts(filename);
+ if (!atts)
+ return -1;
+ print_filename(filename);
+ return 1;
+}
+
+static int init_curses(void)
+{
+ /* Initialize curses */
+ initscr();
+
+ if (LINES <= OFFSET + 4)
+ return -1;
+
+ att_format_cols = (COLS - 2 * OFFSET - 3) / (choice_len + 1);
+ if (att_format_cols < 1)
+ return -1;
+ att_format_lines = (n_choices - 1) / att_format_cols + 1;
+ if (att_format_lines + OFFSET + 4 > LINES)
+ att_format_lines = LINES - OFFSET - 4;
+
+ att_win_lines = att_format_lines + 4;
+ att_win_cols = (choice_len + 1) * att_format_cols + 1;
+ if (att_win_lines + OFFSET > LINES)
+ att_win_lines = LINES - OFFSET - 1;
+ if (att_win_cols + 2 * OFFSET > COLS)
+ att_win_cols = COLS - 2 * OFFSET + 1;
+ //printf ("%i:%i, %i:%i\n", att_format_lines, att_format_cols, att_win_lines, att_win_cols); fflush(stdout); sleep(2);
+
+ cbreak();
+ noecho();
+ keypad(stdscr, TRUE);
+ init_colors();
+ return 1;
+}
+
+int main(int argc, char *argv[])
+{
+ int c, i, ret = EXIT_FAILURE;
+ MENU *my_menu = NULL;
+ char *filename;
+
+ choices = get_all_atts(&n_choices);
+ if (!choices || n_choices <= 0)
+ exit(EXIT_FAILURE);
+ if (argc < 2) {
+ filename = get_current_filename();
+ if (!filename)
+ exit(EXIT_FAILURE);
+ } else
+ filename = strdup(argv[1]);
+ atts = get_atts(filename);
+ if (!atts)
+ goto out;
+ if (init_curses() < 0)
+ goto out;
+
+ /* Initialize items */
+ my_items = (ITEM **)calloc(n_choices + 1, sizeof(ITEM *));
+ for (i = 0; i < n_choices; ++i)
+ my_items[i] = new_item(choices[i], "");
+
+ my_menu = new_menu(my_items);
+ set_item_init(my_menu, _item_init);
+ /* Make the menu multi valued */
+ menu_opts_off(my_menu, O_ONEVALUE);
+ /* Set menu option not to show the description */
+ menu_opts_off(my_menu, O_SHOWDESC);
+
+ /* Create the window to be associated with the menu */
+ att_win = newwin(att_win_lines, att_win_cols, OFFSET, OFFSET);
+ keypad(att_win, TRUE);
+ /* Set main window and sub window */
+ set_menu_win(my_menu, att_win);
+ set_menu_sub(my_menu, derwin(att_win, att_win_lines - 4,
+ att_win_cols - 2, 3, 1));
+ set_menu_format(my_menu, att_format_lines, att_format_cols);
+ //set_menu_format(my_menu, 5, 1);
+ set_menu_mark(my_menu, "");
+
+ /* Print a border around the main window and print a title */
+ wattron(att_win, COLOR_PAIR(COLOR_FRAME));
+ box(att_win, 0, 0);
+ mvwhline(att_win, 2, 1, ACS_HLINE , att_win_cols - 2);
+ mvwaddch(att_win, 2, 0, ACS_LTEE);
+ print_filename(filename);
+ set_menu_fore(my_menu, COLOR_PAIR(COLOR_ACTIVE_ITEM) | A_REVERSE);
+ set_menu_back(my_menu, COLOR_PAIR(COLOR_INACTIVE_ITEM));
+ refresh();
+ post_menu(my_menu);
+
+repeat:
+ wrefresh(att_win);
+ c = getch();
+ switch(c) {
+ case KEY_DOWN:
+ menu_driver(my_menu, REQ_DOWN_ITEM);
+ goto repeat;
+ case KEY_UP:
+ menu_driver(my_menu, REQ_UP_ITEM);
+ goto repeat;
+ case KEY_LEFT:
+ menu_driver(my_menu, REQ_LEFT_ITEM);
+ goto repeat;
+ case KEY_RIGHT:
+ menu_driver(my_menu, REQ_RIGHT_ITEM);
+ goto repeat;
+
+ case 'q':
+ ret = EXIT_SUCCESS;
+ goto out;
+ case 'r':
+ com_refresh_file(filename);
+ refresh_file = 1;
+ _item_init(NULL);
+ goto repeat;
+
+ case ' ':
+ menu_driver(my_menu, REQ_TOGGLE_ITEM);
+ atts_modified = 1;
+ goto repeat;
+ /* Enter */
+ case 10:
+ endwin();
+ if (atts_modified)
+ commit_changes(filename);
+ else
+ printf("Attributes unchanged\n");
+ goto out;
+ default:
+ goto repeat;
+ }
+out:
+ if (my_items) {
+ free_item(my_items[0]);
+ free_item(my_items[1]);
+ }
+ for (i = 0; i < n_choices; i++)
+ free(choices[i]);
+ free(choices);
+ if (my_menu)
+ free_menu(my_menu);
+ if (atts)
+ free(atts);
+ if (filename)
+ free(filename);
+ endwin();
+ exit(ret);
+}
+
--- /dev/null
+/*
+ * Copyright (C) 2004-2006 Andre Noll <maan@systemlinux.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+ */
+
+/** \file dopey.c Simple database tool implementation. Feel free to modify. */
+
+#include <sys/time.h> /* gettimeofday */
+#include "server.cmdline.h"
+#include "server.h"
+#include "db.h"
+#include "error.h"
+#include "net.h"
+#include "string.h"
+
+static int com_dopey(int, int, char **);
+extern struct gengetopt_args_info conf;
+extern struct misc_meta_data *mmd;
+
+static unsigned int num_audio_files, audio_file_count;
+static char **audio_file_list;
+
+static int count_audio_files(__unused const char *dir, __unused const char *name)
+{
+ num_audio_files++;
+ return 1;
+}
+
+static int remember_file(const char *dir, const char *name)
+{
+ if (audio_file_count >= num_audio_files)
+ return -E_FILE_COUNT;
+ audio_file_list[audio_file_count] = make_message("%s/%s", dir, name);
+ audio_file_count++;
+ return 1;
+}
+
+/* array of commands that are supported by this database tool */
+static struct server_command cmds[] = {
+{
+.name = "dopey",
+.handler = com_dopey,
+.perms = 0,
+.description = "about the dopey database tool",
+.synopsis = "dopey",
+.help =
+
+"It's so dumb. It hurts. Don't use it; switch to the mysql database\n"
+"tool instead. OTOH: You typed 'help dopey', so if you serious about\n"
+"that and you really intend to help the dopey database tool, look at\n"
+"my source code, dopey.c, and modify it to make it something useful.\n"
+
+}, {
+.name = NULL,
+}
+};
+
+static int com_dopey(int fd, __unused int argc, __unused char *argv[])
+{
+ return send_buffer(fd, "Please do not use me. I'm too sick to do "
+ "anything for you. Switch me off. Now!\n");
+}
+
+/*
+ * Load a list of all audio files into memory and chose num of them randomly.
+ * Called by server to determine next audio file to be streamed.
+ */
+static char **dopey_get_audio_file_list(unsigned int num)
+{
+ int i, ret;
+ unsigned int len;
+ char **ret_list = NULL; /* what we are going to return */
+
+ audio_file_list = NULL;
+ num_audio_files = 0;
+ /* first run, just count all audio files. dopey */
+ ret = find_audio_files(conf.dopey_dir_arg, count_audio_files);
+ if (ret < 0)
+ goto out;
+ ret = -E_NOTHING_FOUND;
+ if (!num_audio_files)
+ goto out;
+ /* yeah, that doesn't scale, also dopey */
+ audio_file_list = para_malloc(num_audio_files * sizeof(char *));
+ audio_file_count = 0;
+ /* second run (hot dentry cache, hopefully), fill audio_file_list */
+ ret = find_audio_files(conf.dopey_dir_arg, remember_file);
+ if (ret < 0)
+ goto out;
+ /* careful, files might got deleted underneath */
+ num_audio_files = audio_file_count; /* can only decrease */
+ len = MIN(num, num_audio_files);
+ ret = -E_NOTHING_FOUND;
+ if (!len) /* nothing found, return NULL */
+ goto out;
+ /* success, return NULL-terminated list */
+ ret_list = para_calloc((len + 1) * sizeof(char *));
+ for (i = 0; i < len; i++) { /* choose randomly */
+ int r = (int) ((num_audio_files + 0.0) * (rand()
+ / (RAND_MAX + 1.0)));
+ ret_list[i] = para_strdup(audio_file_list[r]);
+ }
+out:
+ if (audio_file_list) {
+ for (i = 0; i < num_audio_files; i++)
+ free(audio_file_list[i]);
+ free(audio_file_list);
+ }
+// if (ret < 0)
+// PARA_ERROR_LOG("%s\n", PARA_STRERROR(-ret));
+ return ret_list;
+}
+
+static void dopey_shutdown(void)
+{
+ PARA_DEBUG_LOG("%s", "thanks for using another dbtool.\n");
+}
+
+/** dopey's (constant) database info text */
+#define DBINFO "dbinfo1:database info? You're kidding. I'm dopey!\ndbinfo2:\ndbinfo3:\n"
+
+/** the dopey init function
+ *
+ * Init all function pointers of \a db, init the dbinfo text and seed the
+ * PRNG.
+ *
+ * \sa struct dbtool, misc_meta_data::dbinfo, mysql.c
+ */
+int dopey_dbtool_init(struct dbtool *db)
+{
+ struct timeval now;
+
+ PARA_INFO_LOG("%s", "registering dopey handlers\n");
+ sprintf(mmd->dbinfo, DBINFO);
+ gettimeofday(&now, NULL);
+ srand(now.tv_usec);
+ db->cmd_list = cmds;
+ db->get_audio_file_list = dopey_get_audio_file_list;
+ db->shutdown = dopey_shutdown;
+ return 1;
+}
--- /dev/null
+/*
+ * Copyright (C) 2006 Andre Noll <maan@systemlinux.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+ */
+
+/** \file error.h list of error messages for all subsystems */
+
+/** \cond list of all subsystems that support the shiny new error facility */
+enum para_subsystem {SS_RECV,
+ SS_NET, SS_ORTP_RECV, SS_AUDIOD, SS_EXEC, SS_CLOSE_ON_FORK, SS_SIGNAL,
+ SS_STRING, SS_DAEMON, SS_STAT, SS_TIME, SS_GRAB_CLIENT, SS_HTTP_RECV,
+ SS_RECV_COMMON, SS_FILTER_CHAIN, SS_WAV, SS_COMPRESS, SS_OGGDEC, SS_FILTER,
+ SS_COMMAND, SS_DOPEY, SS_CRYPT, SS_HTTP_SEND, SS_ORTP_SEND, SS_DB, SS_OGG,
+ SS_MP3, SS_MP3DEC, SS_SERVER, SS_AFS, SS_MYSQL, SS_RINGBUFFER};
+#define NUM_SS (SS_RINGBUFFER + 1)
+extern const char **para_errlist[];
+/** \endcond */
+
+#define NET_ERRORS \
+ PARA_ERROR(SEND, "send error"), \
+ PARA_ERROR(RECV, "receive error"), \
+ PARA_ERROR(SOCKET, "socket error"), \
+ PARA_ERROR(CONNECT, "connect error"), \
+ PARA_ERROR(ACCEPT, "accept error"), \
+ PARA_ERROR(SETSOCKOPT, "failed to set socket options"), \
+ PARA_ERROR(BIND, "bind error"), \
+ PARA_ERROR(NAME_TOO_LONG, "name too long for struct sockaddr_un"), \
+ PARA_ERROR(CHMOD, "failed to set socket mode"), \
+ PARA_ERROR(SENDMSG, "sendmsg() failed"), \
+ PARA_ERROR(RECVMSG, "recvmsg() failed"), \
+ PARA_ERROR(SCM_CREDENTIALS, "did not receive SCM credentials"), \
+ PARA_ERROR(LISTEN, "listen error"), \
+ PARA_ERROR(RECV_PATTERN, "did not receive expected pattern"), \
+ PARA_ERROR(HOST_INFO, "gethostbyname() failed"), \
+
+
+#define ORTP_RECV_ERRORS \
+ PARA_ERROR(MSG_TO_BUF, "failed to extract rtp packet"), \
+ PARA_ERROR(ORTP_SYNTAX, "ottp syntax error"), \
+ PARA_ERROR(TOO_MANY_BAD_CHUNKS, "too many consecutive bad chunks"), \
+ PARA_ERROR(INVALID_HEADER, "invalid header packet"), \
+ PARA_ERROR(OVERRUN, "outout buffer overrun"), \
+
+
+#define HTTP_RECV_ERRORS \
+ PARA_ERROR(SEND_HTTP_REQUEST, "failed to send http request"), \
+ PARA_ERROR(MISSING_OK, "did not receive OK message from peer"), \
+ PARA_ERROR(HTTP_RECV_BUF, "did not receive buffer")
+
+
+#define RECV_ERRORS \
+ PARA_ERROR(RECV_SELECT, "recv select error"), \
+ PARA_ERROR(WRITE_STDOUT, "stdout write error"), \
+
+
+#define RECV_COMMON_ERRORS \
+ PARA_ERROR(RECV_SYNTAX, "recv syntax error"), \
+
+
+#define AUDIOD_ERRORS \
+ PARA_ERROR(WRITE_AUDIO_DATA, "failed to write audio data"), \
+ PARA_ERROR(NO_MORE_SLOTS, "no more empty slots"), \
+ PARA_ERROR(MISSING_COLON, "syntax error: missing colon"), \
+ PARA_ERROR(UNSUPPORTED_AUDIO_FORMAT, "given audio format not supported"), \
+ PARA_ERROR(CLIENT_WRITE, "client write error"), \
+ PARA_ERROR(UCRED_PERM, "permission denied"), \
+ PARA_ERROR(INVALID_AUDIOD_CMD, "invalid command"), \
+
+
+#define FILTER_CHAIN_ERRORS \
+ PARA_ERROR(UNSUPPORTED_FILTER, "given filter not supported"), \
+ PARA_ERROR(BAD_FILTER_OPTIONS, "invalid filter option given"), \
+
+
+#define STAT_ERRORS \
+ PARA_ERROR(TOO_MANY_CLIENTS, "maximal number of stat clients exceeded"), \
+ PARA_ERROR(UNKNOWN_STAT_ITEM, "status item not recognized"), \
+
+
+#define OGGDEC_ERRORS \
+ PARA_ERROR(OGGDEC_READ, "read from media returned an error"), \
+ PARA_ERROR(OGGDEC_NOTVORBIS, "bitstream is not vorbis data"), \
+ PARA_ERROR(OGGDEC_VERSION, "vorbis version mismatch"), \
+ PARA_ERROR(OGGDEC_BADHEADER, "invalid vorbis bitstream header"), \
+ PARA_ERROR(OGGDEC_FAULT, "bug or heap/stack corruption"), \
+ PARA_ERROR(OGGDEC_BADLINK, "invalid stream section or requested link corrupt"), \
+
+
+#define GRAB_CLIENT_ERRORS \
+ PARA_ERROR(PEDANTIC_GRAB, "fd not ready and pedantic grab requested"), \
+ PARA_ERROR(GC_WRITE, "grab client write error"), \
+ PARA_ERROR(INVALID_GRAB_MODE, "invalid grab client mode"), \
+ PARA_ERROR(BAD_GC_SLOT, "invalid slot requested by grab client"), \
+ PARA_ERROR(BAD_GC_FILTER_NUM, "invalid filter number given"), \
+ PARA_ERROR(GC_SYNTAX, "grab client syntax error"), \
+ PARA_ERROR(GC_HELP_GIVEN, ""), /* not really an error */ \
+
+
+#define MP3DEC_ERRORS \
+ PARA_ERROR(MAD_FRAME_DECODE, "mad frame decode error"), \
+ PARA_ERROR(MP3DEC_OVERRUN, "mp3 output buffer overrun"), \
+
+
+#define FILTER_ERRORS \
+ PARA_ERROR(NO_FILTERS, "at least one filter must be given"), \
+ PARA_ERROR(FILTER_SYNTAX, "syntax error"), \
+
+
+#define SIGNAL_ERRORS \
+ PARA_ERROR(SIGNAL_SIG_ERR, "signal() retured SIG_ERR"), \
+ PARA_ERROR(SIGNAL_READ, "read error from signal pipe"), \
+ PARA_ERROR(WAITPID, "waitpid error"), \
+
+
+#define STRING_ERRORS \
+ PARA_ERROR(MKSTEMP, "mkstemp error: unable to create tmp file"), \
+ PARA_ERROR(FCHMOD, "fchmod error: can not set mode"), \
+
+
+#define EXEC_ERRORS \
+ PARA_ERROR(DUP_PIPE, "exec error: can not create pipe"), \
+ PARA_ERROR(NULL_OPEN, "can not open /dev/null"), \
+
+
+#define MP3_ERRORS \
+ PARA_ERROR(FREAD, "fread error"), \
+ PARA_ERROR(FSEEK, "fseek error"), \
+ PARA_ERROR(FRAME, "invalid mp3 frame"), \
+ PARA_ERROR(FRAME_LENGTH, "invalid frame length"), \
+ PARA_ERROR(MP3_NO_FILE, "invalid mp3 file pointer"), \
+ PARA_ERROR(MP3_INFO, "could not read mp3 info"), \
+ PARA_ERROR(MP3_REPOS, "mp3 repositioning error"), \
+ PARA_ERROR(HEADER_FREQ, "invalid header frequency"), \
+ PARA_ERROR(HEADER_BITRATE, "invalid header bitrate"), \
+
+
+#define OGG_ERRORS \
+ PARA_ERROR(OGG_READ, "ogg read error"), \
+ PARA_ERROR(SYNC_PAGEOUT, "ogg sync page-out error (no ogg file?)"), \
+ PARA_ERROR(STREAM_PAGEIN, "ogg stream page-in error (first page)"), \
+ PARA_ERROR(STREAM_PACKETOUT, "ogg stream packet-out error (first packet)"), \
+ PARA_ERROR(VORBIS, "vorbis synthesis header-in error (not vorbis?)"), \
+ PARA_ERROR(OGG_NO_FILE, "invalid ogg file pointer"), \
+ PARA_ERROR(OGG_OPEN, "ov_open error"), \
+ PARA_ERROR(OGG_INFO, "ov_info error"), \
+ PARA_ERROR(OGG_REPOS, "ogg repositioning error"), \
+
+
+#define AFS_ERRORS \
+ PARA_ERROR(AUDIO_FORMAT, "audio format not recognized"), \
+ PARA_ERROR(FSTAT, "failed to fstat() audio file"), \
+
+
+#define DB_ERRORS \
+ PARA_ERROR(GETCWD, "can not get current working directory"), \
+ PARA_ERROR(CHDIR, "can not change directory"), \
+ PARA_ERROR(OPENDIR, "can not open directory"), \
+ PARA_ERROR(LSTAT, "lstat error"), \
+
+
+#define CRYPT_ERRORS \
+ PARA_ERROR(PRIVATE_KEY, "can not read private key"), \
+ PARA_ERROR(PUBLIC_KEY, "can not read public key"), \
+ PARA_ERROR(RSA, "RSA error"), \
+ PARA_ERROR(ENCRYPT, "encrypt error"), \
+ PARA_ERROR(DECRYPT, "decrypt error"), \
+ PARA_ERROR(CHALLENGE, "failed to read challenge"), \
+
+
+#define HTTP_SEND_ERRORS \
+ PARA_ERROR(QUEUE, "packet queue overrun"), \
+ PARA_ERROR(WRITE_OK, "can not check whether fd is writable"), \
+
+
+#define DOPEY_ERRORS \
+ PARA_ERROR(FILE_COUNT, "audio file count exceeded"), \
+ PARA_ERROR(NOTHING_FOUND, "no audio files found"), \
+
+
+#define MYSQL_ERRORS \
+ PARA_ERROR(MYSQL_SYNTAX, "mysql syntax error"), \
+ PARA_ERROR(NOTCONN, "not connected to mysql server"), \
+ PARA_ERROR(TOOBIG, "mysql: file too large"), \
+ PARA_ERROR(NAMETOOLONG, "mysql: name too long"), \
+ PARA_ERROR(QFAILED, "mysql query failed"), \
+ PARA_ERROR(NOROW, "row is NULL"), \
+ PARA_ERROR(NOATTS, "can not get attributes from mysql table"), \
+ PARA_ERROR(NORESULT, "error while fetching mysql result"), \
+ PARA_ERROR(EMPTY_RESULT, "result is empty"), \
+ PARA_ERROR(ESCAPE, "can not escape string"), \
+ PARA_ERROR(GET_AUDIO_FILE, "can not get current audio file"), \
+ PARA_ERROR(GET_STREAM, "can not get current stream"), \
+ PARA_ERROR(NO_STREAM, "no such stream"), \
+ PARA_ERROR(GET_QUERY, "can not get query for specified stream"), \
+ PARA_ERROR(TMPFILE, "error while writing temporary file"), \
+ PARA_ERROR(META, "can not get meta data"), \
+ PARA_ERROR(MYSQL_INIT, "can not initialize mysql connection"), \
+ PARA_ERROR(NO_MYSQL_PASSWD, "fatal: no mysql passord given"), \
+ PARA_ERROR(NO_AF_DIR, "fatal: audio file directory not given"), \
+
+
+#define COMMAND_ERRORS \
+ PARA_ERROR(COMMAND_SYNTAX, "syntax error in command"), \
+ PARA_ERROR(AUTH, "did not receive auth request"), \
+ PARA_ERROR(BAD_DBTOOL, "no such database tool"), \
+ PARA_ERROR(NO_AUDIO_FILE, "no audio file"), \
+ PARA_ERROR(BAD_CMD, "invalid command"), \
+ PARA_ERROR(PERM, "permission denied"), \
+ PARA_ERROR(USERLIST, "failed to open user list file"), \
+ PARA_ERROR(BAD_USER, "you don't exist. Go away."), \
+ PARA_ERROR(LOCK, "lock error"), \
+ PARA_ERROR(SENDER_CMD, "command not supported by this sender"), \
+
+
+/* these do not need error handling (yet) */
+#define SERVER_ERRORS
+#define WAV_ERRORS
+#define COMPRESS_ERRORS
+#define TIME_ERRORS
+#define CLOSE_ON_FORK_ERRORS
+#define DAEMON_ERRORS
+#define ORTP_SEND_ERRORS
+#define RINGBUFFER_ERRORS
+
+
+/**
+ * the subsystem shift
+ *
+ * 255 error codes ought to be enough for every subsystem. Use the higher bits
+ * of the return value to encode the subsystem number
+ */
+#define SS_SHIFT 8
+
+/**
+ * compute the subsystem offset
+ *
+ * It is given by x * 2**8 where \a x is the subsystem number
+ */
+#define SS_OFFSET(ss) (SS_ ## ss << SS_SHIFT)
+
+/**
+ * make the enum of all errors of one subsystem
+ *
+ * As zero should not be an error, we define a dummy enum entry with value
+ * 2**ss. That lets the real errors start at 2**ss + 1.
+ */
+#define SS_ENUM(ss) enum {\
+ E_ ## ss ## _DUMMY = SS_OFFSET(ss), \
+ ss ## _ERRORS}
+
+/**
+ * determine the subsystem number from the error number
+ *
+ * Easy, it's just \a num / 2**8.
+ */
+#define ERRNUM_TO_SS(num) ((num) >> SS_SHIFT)
+
+/**
+ * determine the index of an error number
+ *
+ * Also easy: It's the lower 8 bits of num - 1.
+ */
+#define ERRNUM_TO_INDEX(num) (((1 << SS_SHIFT) - 1) & ((num) - 1))
+
+/**
+ * paraslash's version of strerror(3)
+ *
+ * expands to the error text of \a num (a string constant).
+ */
+#define PARA_STRERROR(num) para_errlist[ERRNUM_TO_SS(num)] [ERRNUM_TO_INDEX(num)]
+
+/**
+ * define the error list for one subsystem
+ *
+ * Used by macros in config.h (generated by configure)
+ */
+#define DEFINE_ERRLIST(ss) const char * ss ## _ERRLIST[] = {ss ## _ERRORS}
+
+/**
+ * activate errors for one subsystem.
+ *
+ * Each executable needs only the error lists of those subssystems it is actually
+ * linked with. We always reserve zeroed-out space for NUM_SS char ** pointers, but
+ * only init those of the needed subsystems. This macro is used by macros in config.h
+ * (generated by configure).
+ */
+#define PARA_ERRLIST(ss) [SS_ ## ss] = ss ## _ERRLIST
+
+/**
+ * This is temporarily defined to expand to its second argument (prefixed by
+ * 'E_') and gets later redefined to expand to the error text only
+ */
+#define PARA_ERROR(err, msg) E_ ## err
+
+/** \cond popcorn time */
+SS_ENUM(ORTP_RECV);
+SS_ENUM(NET);
+SS_ENUM(RECV);
+SS_ENUM(AUDIOD);
+SS_ENUM(EXEC);
+SS_ENUM(CLOSE_ON_FORK);
+SS_ENUM(SIGNAL);
+SS_ENUM(STRING);
+SS_ENUM(DAEMON);
+SS_ENUM(STAT);
+SS_ENUM(TIME);
+SS_ENUM(GRAB_CLIENT);
+SS_ENUM(HTTP_RECV);
+SS_ENUM(RECV_COMMON);
+SS_ENUM(FILTER_CHAIN);
+SS_ENUM(WAV);
+SS_ENUM(COMPRESS);
+SS_ENUM(OGGDEC);
+SS_ENUM(MP3DEC);
+SS_ENUM(FILTER);
+SS_ENUM(MP3);
+SS_ENUM(OGG);
+SS_ENUM(SERVER);
+SS_ENUM(AFS);
+SS_ENUM(COMMAND);
+SS_ENUM(DOPEY);
+SS_ENUM(CRYPT);
+SS_ENUM(HTTP_SEND);
+SS_ENUM(ORTP_SEND);
+SS_ENUM(DB);
+SS_ENUM(MYSQL);
+SS_ENUM(RINGBUFFER);
+/** \endcond */
+#undef PARA_ERROR
+/* rest of the world only sees the error text */
+#define PARA_ERROR(err, msg) msg
--- /dev/null
+/*
+ * Copyright (C) 2003-2006 Andre Noll <maan@systemlinux.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+ */
+
+/** \file exec.c helper functions for spawning new processes */
+#include "para.h"
+#include "close_on_fork.h"
+#include "error.h"
+#include "string.h"
+
+/**
+ * spawn a new process and redirect fd 0, 1, and 2
+ *
+ * \param pid will hold the pid of the created process upon return
+ * \param file path of the executable to execute
+ * \param args the argument array for the command
+ * \param fds a pointer to a value-result array
+ *
+ * This function uses fork/exec to create a new process. \a fds must be a
+ * pointer to three integers, corresponding to stdin, stdout and stderr
+ * respectively. It specifies how to deal with fd 0, 1, 2 in the child. The
+ * contents of \a fds are interpreted as follows:
+ *
+ * - fd[i] < 0: leave fd \a i alone
+ * - fd[i] = 0: dup fd \a i to /dev/null
+ * - fd[i] > 0: create a pipe and dup i to one end of that pipe.
+ * Upon return, fd[i] contains the file descriptor of the pipe.
+ *
+ * In any case, all unneeded filedescriptors are closed.
+ *
+ * \return Negative on errors, positive on success.
+ *
+ * \sa null(4), pipe(2), dup2(2), fork(2), exec(3)
+ */
+int para_exec(pid_t *pid, const char *file, char *const args[], int *fds)
+{
+ int ret, in[2] = {-1, -1}, out[2] = {-1, -1}, err[2] = {-1, -1},
+ null = -1; /* ;) */
+
+ ret = -E_DUP_PIPE;
+ if (fds[0] > 0 && pipe(in) < 0)
+ goto err_out;
+ if (fds[1] > 0 && pipe(out) < 0)
+ goto err_out;
+ if (fds[2] > 0 && pipe(err) < 0)
+ goto err_out;
+ if (!fds[0] || !fds[1] || !fds[2]) {
+ ret = -E_NULL_OPEN;
+ null = open("/dev/null", O_RDONLY);
+ if (null < 0)
+ goto err_out;
+ }
+ if ((*pid = fork()) < 0)
+ exit(EXIT_FAILURE);
+ if (!(*pid)) { /* child */
+ close_listed_fds(); /* close unneeded fds */
+ if (fds[0] >= 0) {
+ if (fds[0]) {
+ close(in[1]);
+ if (in[0] != STDIN_FILENO)
+ dup2(in[0], STDIN_FILENO);
+ } else
+ dup2(null, STDIN_FILENO);
+ }
+ if (fds[1] >= 0) {
+ if (fds[1]) {
+ close(out[0]);
+ if (out[1] != STDOUT_FILENO)
+ dup2(out[1], STDOUT_FILENO);
+ } else
+ dup2(null, STDOUT_FILENO);
+ }
+ if (fds[2] >= 0) {
+ if (fds[2]) {
+ close(err[0]);
+ if (err[1] != STDERR_FILENO)
+ dup2(err[1], STDERR_FILENO);
+ } else
+ dup2(null, STDERR_FILENO);
+ }
+ if (null >= 0)
+ close(null);
+ execvp(file, args);
+ _exit(EXIT_FAILURE);
+ }
+ if (fds[0] > 0) {
+ close(in[0]);
+ *fds = in[1];
+ }
+ if (fds[1] > 0) {
+ close(out[1]);
+ *(fds + 1) = out[0];
+ }
+ if (fds[2] > 0) {
+ close(err[1]);
+ *(fds + 2) = err[0];
+ }
+ if (null >= 0)
+ close(null);
+ return 1;
+err_out:
+ if (err[0] >= 0)
+ close(err[0]);
+ if (err[1] >= 0)
+ close(err[1]);
+ if (out[0] >= 0)
+ close(out[0]);
+ if (out[1] >= 0)
+ close(out[1]);
+ if (in[0] >= 0)
+ close(in[0]);
+ if (in[1] >= 0)
+ close(in[1]);
+ if (null >= 0)
+ close(null);
+ return ret;
+}
+
+
+/**
+ * exec the given command
+ *
+ * \param pid the same meaning as in para_exec()
+ * \param cmdline holds the command and its arguments, seperated by spaces
+ * \param fds the same meaning as in para_exec()
+ *
+ * A wrapper for para_exec() which calls split_args() to seperate
+ * the command line arguments.
+ *
+ * \return positive on success, negative on errors
+ */
+int para_exec_cmdline_pid(pid_t *pid, char *cmdline, int *fds)
+{
+ int argc, ret;
+ char **argv, *tmp = para_strdup(cmdline);
+
+ if (!tmp)
+ exit(EXIT_FAILURE);
+ argc = split_args(tmp, &argv, ' ');
+ ret = para_exec(pid, argv[0], argv, fds);
+ free(argv);
+ free(tmp);
+ return ret;
+}
+
+/**
+ * check whether a file exists
+ *
+ * \param fn the file name
+ *
+ * \return Non-zero iff file exists.
+ */
+int file_exists(const char *fn)
+{
+ struct stat statbuf;
+
+ return !stat(fn, &statbuf);
+}
--- /dev/null
+/*
+ * Copyright (C) 1998-2005 Andre Noll <maan@systemlinux.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+ */
+
+/** \file fade.c a volume fader and alarm clock */
+
+#include "fade.cmdline.h"
+#include "para.h"
+
+#include <ctype.h>
+#include <curses.h>
+#include <stdlib.h> /* EXIT_SUCCESS */
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <limits.h>
+#include <linux/soundcard.h>
+#include "string.h"
+
+
+struct gengetopt_args_info args_info;
+
+void para_log(__unused int ll, char *fmt,...)
+{
+ va_list argp;
+ time_t t1;
+ struct tm *tm;
+
+ time(&t1);
+ tm = localtime(&t1);
+ printf("%d:%02d:%02d ", tm->tm_hour, tm->tm_min, tm->tm_sec);
+ va_start(argp, fmt);
+ vprintf(fmt, argp);
+ va_end(argp);
+}
+
+/*
+ * open mixer device
+ */
+static int open_mixer(void)
+{
+ int mixer_fd;
+
+ if ((mixer_fd = open(args_info.mixer_device_arg, O_RDWR, 0)) < 0)
+ return -1;
+ return mixer_fd;
+}
+
+/*
+ * get volume via mixer_fd
+ */
+static int do_get_vol(int mixer_fd)
+{
+ int volume;
+
+ if (ioctl(mixer_fd, MIXER_READ(SOUND_MIXER_VOLUME), &volume) < 0)
+ return -1;
+ /* take the mean value of left and right volume */
+ return (volume % 256 + (volume >> 8)) / 2;
+}
+
+/*
+ * open mixer, get volume and close mixer
+ */
+static int get_vol(void)
+{
+ int mixer_fd;
+ int volume;
+
+ mixer_fd = open_mixer();
+ if (mixer_fd < 0)
+ return -1;
+ volume = do_get_vol(mixer_fd);
+ close(mixer_fd);
+ return volume;
+}
+
+/*
+ * set volume via mixer_fd
+ */
+static int do_set_vol(int mixer_fd, int volume)
+{
+ int tmp;
+ tmp = (volume << 8) + volume;
+ if (ioctl(mixer_fd, MIXER_WRITE(SOUND_MIXER_VOLUME), &tmp) < 0)
+ return 0;
+ return 1;
+}
+
+/*
+ * open mixer, set volume and close mixer
+ */
+static int set_vol(int volume)
+{
+ int mixer_fd;
+ int ret;
+
+ mixer_fd = open_mixer();
+ ret = 0;
+ if (mixer_fd < 0)
+ goto out;
+ if (!do_set_vol(mixer_fd, volume))
+ goto out;
+ ret = 1;
+ close(mixer_fd);
+out:
+ return ret;
+}
+
+/*
+ * Open mixer, get volume, fade to new_vol in secs seconds and
+ * close mixer
+ */
+static int fade(int new_vol, unsigned int secs)
+{
+ int vol, mixer_fd = -1, diff, incr, ret;
+ struct timespec ts;
+ unsigned long long tmp, tmp2; /* Careful with that axe, Eugene! */
+
+ PARA_NOTICE_LOG("fading to %d in %d seconds\n", new_vol, secs);
+ ret = 0;
+ if (!secs)
+ goto out;
+ mixer_fd = open_mixer();
+ if (mixer_fd < 0)
+ goto out;
+ vol = do_get_vol(mixer_fd);
+ if (vol < 0)
+ goto out;
+ diff = new_vol - vol;
+ if (!diff) {
+ sleep(secs);
+ ret = 1;
+ goto out;
+ }
+ incr = diff > 0? 1: -1;
+ diff = diff > 0? diff: -diff;
+ tmp = secs * 1000 / diff;
+ tmp2 = tmp % 1000;
+ while ((new_vol - vol) * incr > 0) {
+ ts.tv_nsec = tmp2 * 1000000; /* really nec ?*/
+ ts.tv_sec = tmp / 1000; /* really nec ?*/
+ //printf("ts.tv_sec: %i\n", ts.tv_nsec);
+ vol += incr;
+ if (!do_set_vol(mixer_fd, vol))
+ goto out;
+ //printf("vol = %i\n", vol);
+ nanosleep(&ts, NULL);
+ }
+out:
+ if (mixer_fd >= 0)
+ close(mixer_fd);
+ return 1;
+}
+
+static int client_cmd(char *cmd,...)
+{
+ int ret, fds[3] = {0, 0, 0};
+ pid_t pid;
+ char *cmdline = make_message(BINDIR "/para_client %s", cmd);
+ PARA_INFO_LOG("%s", cmdline);
+ ret = para_exec_cmdline_pid(&pid, cmdline, fds);
+ free(cmdline);
+ return ret;
+}
+
+/*
+ * sleep
+ */
+static void sweet_dreams(void)
+{
+ time_t t1, wake_time_epoch;
+ unsigned int delay;
+ struct tm *tm;
+ int hour = args_info.wake_hour_arg;
+ int min = args_info.wake_min_arg;
+ char *fa_stream = args_info.fa_stream_arg;
+ char *wake_stream = args_info.wake_stream_arg;
+ //char *current_stream = stat_items[STREAM].content;
+ int wf = args_info.wake_fade_arg;
+ int sf = args_info.fa_fade_arg;
+ int wv = args_info.wake_vol_arg;
+ int sv = args_info.fa_vol_arg;
+ int iv = args_info.sleep_ivol_arg;
+ char *cmd, *sleep_stream = args_info.sleep_stream_given?
+ args_info.sleep_stream_arg : NULL;
+
+ if (sf) {
+ PARA_INFO_LOG("initial volume: %d\n", iv);
+ set_vol(iv);
+ cmd = make_message("csp %s\n", fa_stream);
+ client_cmd(cmd);
+ free(cmd);
+ fade(sv, sf);
+ }
+ if (sleep_stream) {
+ cmd = make_message("csp %s\n", sleep_stream);
+ client_cmd(cmd);
+ free(cmd);
+ } else
+ client_cmd("stop");
+ if (!wf)
+ return;
+ /* calculate wake time */
+ time(&t1);
+ tm = localtime(&t1);
+ if (tm->tm_hour > hour || (tm->tm_hour == hour && tm->tm_min> min)) {
+ /* wake time is tomorrow */
+ t1 += 86400;
+ tm = localtime(&t1);
+ t1 -= 86400;
+ }
+ tm->tm_hour = hour;
+ tm->tm_min = min;
+ tm->tm_sec = 0;
+ wake_time_epoch = mktime(tm);
+ PARA_INFO_LOG("waketime: %s", asctime(tm));
+ while (wake_time_epoch > t1 + wf) {
+ delay = wake_time_epoch - t1 - wf;
+ PARA_INFO_LOG("sleeping %u seconds (%u:%02u)\n",
+ delay, delay / 3600,
+ (delay % 3600) / 60);
+ sleep(delay);
+ time(&t1);
+ }
+ cmd = make_message("csp %s\n", wake_stream);
+ client_cmd(cmd);
+ free(cmd);
+ fade(wv, wf);
+ PARA_INFO_LOG("%s", "fade complete, returning\n");
+}
+
+static void snooze(void)
+{
+ if (get_vol() < args_info.snooze_out_vol_arg)
+ set_vol(args_info.snooze_out_vol_arg);
+ else
+ fade(args_info.snooze_out_vol_arg, args_info.snooze_out_fade_arg);
+ client_cmd("pause");
+ PARA_NOTICE_LOG("%d seconds snooze time...\n", args_info.snooze_time_arg);
+ sleep(args_info.snooze_time_arg);
+ client_cmd("play");
+ fade(args_info.snooze_in_vol_arg, args_info.snooze_in_fade_arg);
+}
+
+static int configfile_exists(void)
+{
+ static char *config_file;
+
+ if (!args_info.config_file_given) {
+ char *home = para_homedir();
+ const char *conf = ".paraslash/fade.conf";
+ free(config_file);
+ config_file = make_message("%s/%s", home, conf);
+ free(home);
+ args_info.config_file_arg = config_file;
+ }
+ return file_exists(args_info.config_file_arg);
+}
+
+
+int main(int argc, char *argv[])
+{
+ int ret;
+
+ if (cmdline_parser(argc, argv, &args_info))
+ exit(EXIT_FAILURE);
+ ret = configfile_exists();
+ if (!ret && args_info.config_file_given) {
+ PARA_EMERG_LOG("can not read config file %s\n",
+ args_info.config_file_arg);
+ exit(EXIT_FAILURE);
+ }
+ if (ret)
+ cmdline_parser_configfile(args_info.config_file_arg,
+ &args_info, 0, 0, 0);
+ if ((ret = open_mixer()) < 0) {
+ PARA_EMERG_LOG("can not open mixer device %s.",
+ args_info.mixer_device_arg);
+ exit(EXIT_FAILURE);
+ } else
+ close(ret);
+ ret = 0;
+ setlinebuf(stdout);
+ if (!strcmp(args_info.mode_arg, "sleep")) {
+ sweet_dreams();
+ goto out;
+ }
+ if (!strcmp(args_info.mode_arg, "fade")) {
+ fade(args_info.fade_vol_arg, args_info.fade_time_arg);
+ goto out;
+ }
+ if (!strcmp(args_info.mode_arg, "snooze")) {
+ snooze();
+ goto out;
+ }
+ ret = -1;
+out:
+ return ret;
+}
--- /dev/null
+section "general options"
+option "mode" o "{sleep|fade|snooze}" string default="sleep" no
+option "config_file" c "(default='~/.paraslash/fade.conf')" string typestr="filename" no
+option "mixer_device" m "mixer device file" string typestr="device" default="/dev/mixer" no
+
+section "sleep options (only relevant in sleep mode)"
+option "sleep_ivol" - "set initial volume before doing anything else" int typestr="volume" default="60" no
+option "fa_stream" - "fall asleep stream. Change to this stream right after setting the volume" string typestr="streamname" default="fa" no
+option "fa_fade" - "fall asleep fading time, no fading if set to 0" int typestr="seconds" default="1800" no
+option "fa_vol" - "volume to fade to" int typestr="volume" default="20" no
+option "sleep_stream" - "change to this stream after fading, stop playing if unset" string typestr="streamname" default="sleep" no
+option "wake_hour" H "(0-23)" int default="8" no
+option "wake_min" M "(0-59)" int default="0" no
+option "wake_stream" - "changed to on waketime" string typestr="streamname" default="wake" no
+option "wake_fade" - "no fading in if set to 0" int typestr="seconds" default="1200" no
+option "wake_vol" - "vol to fade to at waketime" int typestr="volume" default="80" no
+
+section "snooze options"
+option "snooze_out_fade" - "fade out time" int typestr="seconds" default="30" no
+option "snooze_out_vol" - "vol to fade to before snooze" int typestr="volume" default="20" no
+option "snooze_time" - "delay" int typestr="seconds" default="600" no
+option "snooze_in_fade" - "fade in time" int typestr="seconds" default="180" no
+option "snooze_in_vol" - "vol to fade to after snooze" int typestr="volume" default="80" no
+
+section "fade options"
+option "fade_vol" f "volume to fade to" int typestr="volume" default="50" no
+option "fade_time" t "time to fade in" int typestr="seconds" default="5" no
--- /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 filter.c the stand-alone filter program */
+
+#include "gcc-compat.h"
+#include "para.h"
+
+#include "filter.cmdline.h"
+#include "list.h"
+#include "filter.h"
+#include "error.h"
+#include "string.h"
+
+INIT_FILTER_ERRLISTS;
+
+#define INBUF_SIZE 32 * 1024
+
+static struct filter_chain_info filter_chain_info_struct;
+static struct filter_chain_info *fci = &filter_chain_info_struct;
+
+struct gengetopt_args_info conf;
+
+__printf_2_3 void para_log(int ll, char* fmt,...)
+{
+ va_list argp;
+
+ /* ignore log message if loglevel is not high enough */
+ if (ll < conf.loglevel_arg)
+ return;
+ va_start(argp, fmt);
+ vfprintf(stderr, fmt, argp);
+ va_end(argp);
+}
+
+static char *inbuf;
+static size_t loaded;
+static int eof;
+
+static int init_active_filter_list(void)
+{
+ int i, filter_num;
+ struct filter_node *fn;
+
+ INIT_LIST_HEAD(&fci->filters);
+
+ fci->inbuf = inbuf;
+ fci->in_loaded = &loaded;
+ fci->eof = &eof;
+
+ for (i = 0; i < conf.filter_given; i++) {
+ char *fa = para_strdup(conf.filter_arg[i]);
+ fn = para_calloc(sizeof(struct filter_node));
+ filter_num = check_filter_arg(fa, &fn->conf);
+ if (filter_num < 0) {
+ free(fn);
+ return filter_num;
+ }
+ fn->fci = fci;
+ INIT_LIST_HEAD(&fn->callbacks);
+ fn->filter = &filters[filter_num];
+ PARA_DEBUG_LOG("adding %s to filter chain\n", fn->filter->name);
+ list_add_tail(&fn->node, &fci->filters);
+ }
+ if (list_empty(&fci->filters))
+ return -E_NO_FILTERS;
+ return 1;
+}
+
+static void open_filters(void)
+{
+ struct filter_node *fn;
+
+ list_for_each_entry(fn, &fci->filters, node) {
+ fn->filter->open(fn);
+ PARA_INFO_LOG("opened %s filter\n", fn->filter->name);
+ fci->outbuf = fn->buf;
+ fci->out_loaded = &fn->loaded;
+ }
+}
+
+static int parse_config(int argc, char *argv[])
+{
+ static char *cf; /* config file */
+ struct stat statbuf;
+ int i;
+
+ if (cmdline_parser(argc, argv, &conf))
+ return -E_FILTER_SYNTAX;
+ if (!cf) {
+ char *home = para_homedir();
+ cf = make_message("%s/.paraslash/filter.conf", home);
+ free(home);
+ }
+ if (!stat(cf, &statbuf)) {
+ if (cmdline_parser_configfile(cf, &conf, 0, 0, 0))
+ return -E_FILTER_SYNTAX;
+ }
+ if (!conf.list_filters_given)
+ return 1;
+ printf("available filters: ");
+ for (i = 0; filters[i].name; i++)
+ printf("%s%s", i? " " : "", filters[i].name);
+ printf("\nTry para_filter -f<filtername>:-h for help on <filtername>\n");
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char *argv[])
+{
+ int converted, ret;
+ char *ib, *ob; /* input/output buffer */
+ size_t *il, *ol; /* number of loaded bytes in input/output buffer */
+
+ filter_init(filters);
+ ret = parse_config(argc, argv);
+ if (ret < 0)
+ goto out;
+ inbuf = para_malloc(INBUF_SIZE);
+ ret = init_active_filter_list();
+ if (ret < 0)
+ goto out;
+ open_filters();
+ ib = fci->inbuf;
+ ob = fci->outbuf;
+ il = fci->in_loaded;
+ ol = fci->out_loaded;
+ PARA_DEBUG_LOG("ib %p in, ob: %p\n", ib, ob);
+again:
+ if (*il < INBUF_SIZE && !eof) {
+ ret = read(STDIN_FILENO, ib + *il, INBUF_SIZE - *il);
+ PARA_DEBUG_LOG("read %d/%d\n", ret, INBUF_SIZE - *il);
+ if (ret < 0)
+ goto out;
+ if (!ret)
+ eof = 1;
+ *il += ret;
+ }
+ ret = filter_io(fci);
+ if (ret < 0)
+ goto out;
+ converted = ret;
+ if (*ol) {
+ ret = write(STDOUT_FILENO, ob, *ol);
+ PARA_DEBUG_LOG("wrote %d/%d\n", ret, *ol);
+ if (ret <= 0)
+ goto out;
+ *ol -= ret;
+ if (*ol) {
+ PARA_NOTICE_LOG("short write: %d bytes left\n", *ol);
+ memmove(ob, ob + ret, *ol);
+ }
+ }
+ if (!eof || converted)
+ goto again;
+ ret = 0;
+out:
+ if (ret < 0)
+ PARA_EMERG_LOG("%s\n", PARA_STRERROR(-ret));
+ close_filters(fci);
+ return ret;
+}
--- /dev/null
+option "loglevel" l "set loglevel (0-6)" int typestr="level" default="4" no
+option "filter" f "Specify filter.
+
+May be given multiple times to 'pipe' the stream
+through arbitrary many filters in an efficient
+way. The same filter may appear more than once,
+order matters.
+
+Filter options may be specified for each '-f'
+option separately. Insinde these options ':'
+must be used as the separator instead of white
+space. Example:
+
+ -f compress:--anticlip:--volume:2
+"
+string typestr="filter_spec" no multiple
+
+option "list_filters" L "print list of available filters and exit" flag off
--- /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 filter.h filter-related structures and exported symbols from filter_chain.c */
+
+/**
+ * describes one running instance of a chain of filters
+ *
+ */
+struct filter_chain_info {
+/**
+ *
+ *
+ * the number of channels of the current stream
+ *
+ * Set by the decoding filter
+ */
+ unsigned int channels;
+/**
+ *
+ *
+ * current samplerate in Hz
+ *
+ * Set by the decoding filter
+ */
+ unsigned int samplerate;
+/**
+ *
+ *
+ * the list containing all filter nodes in this filter chain
+ */
+ struct list_head filters;
+/**
+ *
+ *
+ * the input buffer of the filter chain
+ *
+ * This is set to point to the output buffer of the receiving application (the
+ * buffer used to read from stdin for para_filter; the output buffer of the
+ * current receiver for para_audiod)
+ */
+ char *inbuf;
+/**
+ *
+ *
+ * the output buffer of the filter chain
+ *
+ * Points to the output buffer of the last filter in the filter chain
+**/
+ char *outbuf;
+/**
+ *
+ *
+ * pointer to variable containing the number of bytes loaded in the input buffer
+ */
+ size_t *in_loaded;
+/**
+ *
+ *
+ * pointer to variable containing the number of bytes loaded in the output buffer
+ */
+ size_t *out_loaded;
+/**
+ *
+ *
+ * non-zero if end of file was encountered
+ */
+ int *eof;
+/**
+ *
+ *
+ * non-zero if an error occured
+ */
+ int error;
+};
+
+/**
+ * describes one running instance of a filter
+*/
+struct filter_node {
+/**
+ *
+ *
+ * a pointer to the corresponding filter struct
+ */
+ struct filter *filter;
+/**
+ *
+ *
+ * the filter chain this filter node belongs to
+ */
+ struct filter_chain_info *fci;
+/**
+ *
+ *
+ * the position of the filter in the corresponding filter chain
+ *
+ * all filters that make up the filter chains are organized in a doubly
+ * linked list.
+ */
+ struct list_head node;
+/**
+ *
+ *
+ * each filter may store any filter-specific information about the particular
+ * instance of the filter here.
+ */
+ void *private_data;
+/**
+ *
+ *
+ * the output buffer
+ */
+ char *buf;
+/**
+ * the size of the output buffer
+ */
+ size_t bufsize;
+/**
+ *
+ *
+ * the number of bytes currently loaded in \a buf
+ */
+ size_t loaded;
+/**
+ *
+ *
+ * the list of registered callbacks
+ */
+ struct list_head callbacks;
+/**
+ *
+ * a pointer to the configuration of this instance
+ */
+ void *conf;
+};
+
+/**
+ * used to manage grab clients
+ *
+ * An application using paraslash's filter subsystem may register any number of
+ * callbacks for each filter_node. It is possible to attach a filter callback
+ * while the filter is running. This is used for stream grabbing in
+ * para_audiod: Whenever a client sends the 'grab' command, para_audiod adds a
+ * filter callback to the list of callbacks for the filter node specified in
+ * the grab command.
+ */
+struct filter_callback {
+/**
+ *
+ *
+ * all callbacks are organized in a doubly linked list
+ */
+ struct list_head node;
+/**
+ *
+ *
+ * private data
+ *
+ * May be initialized by the application before registering the callback. This
+ * pointer is not used by the filter subsystem. It is provided for use within
+ * the input/ouput/close callback functions.
+ */
+ void *data;
+/**
+ *
+ *
+ * the input callback
+ *
+ * In not \p NULL, the filter subsystem calls this function whenever the filter
+ * consumed some or all of its input buffer. A pointer to the buffer of consumed
+ * data, its length and a pointer to the own \a filter_callback structure are passed
+ * to \a input_cb. The input callback is expected to return a negative value on errors.
+ */
+ int (*input_cb)(char *buf, size_t len, struct filter_callback *fc);
+/**
+ *
+ *
+ * the output callback
+ *
+ * If not NULL, this is called whenever the filter produces output. A pointer
+ * to the output data, its length and a pointer to the own \a filter_callback
+ * structure are passed to \a output_cb. Like the input callback, the output
+ * callback is expected to return a negative value on errors.
+ */
+ int (*output_cb)(char *buf, size_t len, struct filter_callback *fc);
+/**
+ *
+ *
+ * the callback close function
+ *
+ * This gets called whenever the input/ouput callback returned an error, or if
+ * the filter chain is going to be destroyed, e.g. because the end of the
+ * stream was encounterd. It is assumed to succeed.
+ */
+ void (*close)(struct filter_callback *fc);
+};
+
+
+void close_filters(struct filter_chain_info *fci);
+int filter_io(struct filter_chain_info *fci);
+void filter_init(struct filter *all_filters);
+int check_filter_arg(char *filter_arg, void **conf);
+int del_filter_callback(struct filter_callback *fcb);
+
+/**
+ * the structure associated with a paraslash filter
+ *
+ * Paraslash filters are "modules" which are used to transform an audio stream.
+ * struct filter contains pointers to functions that must be supplied by the
+ * filter code in order to be used by the driving application (currently
+ * para_audiod and para_filter).
+ *
+ * Note: As several instances of the same filter may be running at the same
+ * time, all these filter functions must be reentrant; no static non-constant
+ * variables may be used.
+ * \sa mp3dec.c, oggdec.c, wav.c, compress.c, filter_node
+ */
+struct filter {
+/**
+ *
+ *
+ * the name of the filter
+ */
+const char *name;
+/**
+ *
+ *
+ * pointer to the filter init routine
+ *
+ * This function is only called once at startup. It must initialize the
+ * other non-optional function pointers of \a f.
+ */
+void (*init)(struct filter *f);
+/**
+ *
+ *
+ * open one instance of this filter
+ *
+ * This should allocate the output buffer of the given filter node and do any
+ * other filter-specific preparations like initializing the private_data member
+ * of \a fn suitably. The open function is assumed to succeed.
+ */
+void (*open)(struct filter_node *fn);
+/**
+ *
+ *
+ * convert (filter) the given data
+ *
+ * Pointer to the converting function of the filter. It should convert the
+ * given input buffer \a inbuf which is of length \a len to the previoulsy
+ * reserved output buffer of \a fn. On success, it must return the number of
+ * bytes it consumed from \a inbuf. On errors, a negative number indicating the
+ * kind of the error must be returned.
+ *
+ * A zero return value just means that nothing was converted (probably because
+ * the input buffer was too small). This is not interpreted as an error.
+ */
+ssize_t (*convert)(char *inbuf, size_t len, struct filter_node *fn);
+/**
+ *
+ *
+ * close one instance of this filter
+ *
+ * Free all resources of associated with \a fn that were previously allocated
+ * by the open() function.
+ */
+void (*close)(struct filter_node *fn);
+/**
+ *
+ *
+ * print the help text for this filter and exit
+ *
+ * This is optional and it is not necessary to initialize this pointer if
+ * the filter does not have a help text.
+ */
+void (*print_help)(void);
+/**
+ *
+ *
+ * a pointer to the filter's command line parser
+ *
+ * If this optional function pointer is not NULL, any filter options are passed
+ * from the main propgram to this command line parser once at application
+ * startup. The command line parser should check its command line options given
+ * by \a argc and \a argv and abort on errors. On success, it should return a
+ * pointer to the filter-specific configuration data determined by \a argc and
+ * \a argv.
+ */
+void *(*parse_config)(int argc, char **argv);
+};
+/** \cond */
+extern struct filter filters[];
+#define DECLARE_EXTERN_FILTER_INIT(name) \
+ extern void name ## _init(struct filter *f)
+
+#define FILTER_INIT(filter) { \
+ .name = #filter, \
+ .init = filter ## _init, \
+ .parse_config = NULL, \
+ .print_help = NULL \
+},
+
+/* filters that are always present */
+DECLARE_EXTERN_FILTER_INIT(wav);
+/* wav is always the first filter */
+#define WAV_FILTER_NUM 0
+DECLARE_EXTERN_FILTER_INIT(compress);
+
+/* next the optional filters */
+#ifdef HAVE_MAD
+DECLARE_EXTERN_FILTER_INIT(mp3dec);
+#define MP3DEC_FILTER FILTER_INIT(mp3dec)
+#else
+#define MP3DEC_FILTER
+#endif
+
+#ifdef HAVE_OGGVORBIS
+DECLARE_EXTERN_FILTER_INIT(oggdec);
+#define OGGDEC_FILTER FILTER_INIT(oggdec)
+#else
+#define OGGDEC_FILTER
+#endif
+
+/*
+ * a macro that defines an array of all available filters
+ */
+#define DEFINE_FILTER_ARRAY(fa) struct filter fa[] = { \
+ FILTER_INIT(wav) \
+ FILTER_INIT(compress) \
+ MP3DEC_FILTER \
+ OGGDEC_FILTER \
+ { .name = NULL } };
+/** \endcond */
--- /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 filter_chain.c common helper functions for filter input/output */
+
+#include "gcc-compat.h"
+#include "para.h"
+#include "list.h"
+#include "filter.h"
+#include "error.h"
+#include "string.h"
+
+DEFINE_FILTER_ARRAY(filters);
+
+/**
+ * call the init function of each supported filter
+ *
+ * \param all_filters the array of all supported filters
+ * \sa filter::init
+ */
+void filter_init(struct filter *all_filters)
+{
+ struct filter *f;
+
+ for (f = all_filters; f->name; f++)
+ f->init(f);
+}
+
+/**
+ * close and destroy a filter callback
+ *
+ * \param fcb the filter callback to close
+ *
+ * This removes \a fcb from the list of filter callbacks and calls
+ * the close callback associated with \a fcb.
+ */
+static void close_filter_callback(struct filter_callback *fcb)
+{
+ PARA_NOTICE_LOG("closing filter_callback %p, data: %p\n", fcb, fcb->data);
+ list_del(&fcb->node);
+ fcb->close(fcb);
+}
+
+/**
+ * close all callbacks of a filter node
+ *
+ * \param fn the filter node which contains the filter callbacks to be closed
+ *
+ * Call close_filter_callback() for each entry in the filter callback list
+ * of \a fn.
+ */
+static void close_callbacks(struct filter_node *fn)
+{
+ struct filter_callback *fcb, *tmp;
+
+ list_for_each_entry_safe(fcb, tmp, &fn->callbacks, node)
+ close_filter_callback(fcb);
+}
+
+static void call_callbacks(struct filter_node *fn, char *inbuf, size_t inlen,
+ char *outbuf, size_t outlen)
+{
+ struct filter_callback *fcb, *tmp;
+ list_for_each_entry_safe(fcb, tmp, &fn->callbacks, node) {
+ int ret;
+ if (inlen && fcb->input_cb) {
+ ret = fcb->input_cb(inbuf, inlen, fcb);
+ if (ret < 0) {
+ close_filter_callback(fcb);
+ continue;
+ }
+ }
+ if (!outlen || !fcb->output_cb)
+ continue;
+ ret = fcb->output_cb(outbuf, outlen, fcb);
+ if (ret < 0)
+ close_filter_callback(fcb);
+ }
+}
+
+/**
+ * call the convert function of each filter
+ *
+ * \param fci the filter chain containing the list of filter nodes.
+ *
+ * This is the core function of the filter subsystem. It loops over the list of
+ * filter nodes determined by \a fci and calls the filter's convert function if
+ * there is input available for the filter node in question. If the convert
+ * function consumed some or all of its input data, all registered input
+ * callbacks are called. Similarly, if a convert function produced output, all
+ * registerd output callbacks get called.
+ *
+ * \return The sum of output bytes produced by the convert functions on success,
+ * negative return value on errors.
+ *
+ * \sa filter_node, filter#convert, filter_callback
+ */
+int filter_io(struct filter_chain_info *fci)
+{
+ struct filter_node *fn;
+ char *ib;
+ size_t *loaded;
+ int conv, conv_total = 0;
+again:
+ ib = fci->inbuf;
+ loaded = fci->in_loaded;
+ conv = 0;
+ list_for_each_entry(fn, &fci->filters, node) {
+ int ret;
+ if (*loaded && fn->loaded < fn->bufsize) {
+ size_t old_fn_loaded = fn->loaded;
+ PARA_DEBUG_LOG("fc %p loaded: %d, calling %s convert\n", fci, *loaded, fn->filter->name);
+ ret = fn->filter->convert(ib, *loaded, fn);
+ if (ret < 0) {
+ if (!fci->error)
+ fci->error = -ret;
+ return ret;
+ }
+ call_callbacks(fn, ib, ret, fn->buf + old_fn_loaded, fn->loaded - old_fn_loaded);
+ *loaded -= ret;
+ conv += ret;
+ if (*loaded && ret) {
+ PARA_DEBUG_LOG("moving %d bytes in input buffer for %s filter\n",
+ *loaded, fn->filter->name);
+ memmove(ib, ib + ret, *loaded);
+ }
+ }
+ ib = fn->buf;
+ loaded = &fn->loaded;
+ }
+// PARA_DEBUG_LOG("loaded: %d\n", *loaded);
+ conv_total += conv;
+ if (conv)
+ goto again;
+ return conv_total;
+}
+
+/**
+ * close all filter nodes and its callbacks
+ *
+ * \param fci the filter chain to close
+ *
+ * For each filter node determined by \a fci, call the close function of each
+ * registered filter callback as well as the close function of the
+ * corresponding filter. Free all resources and destroy all callback lists and
+ * the filter node list.
+ *
+ * \sa filter::close, filter_callback::close
+ */
+void close_filters(struct filter_chain_info *fci)
+{
+ struct filter_node *fn, *tmp;
+
+ if (!fci)
+ return;
+ PARA_DEBUG_LOG("closing filter chain %p\n", fci);
+ list_for_each_entry_safe(fn, tmp, &fci->filters, node) {
+ PARA_NOTICE_LOG("closing %s filter callbacks (fci %p, fn %p)\n", fn->filter->name, fci, fn);
+ close_callbacks(fn);
+ PARA_NOTICE_LOG("closing %s filter (fci %p, fn %p)\n", fn->filter->name, fci, fn);
+ fn->filter->close(fn);
+ list_del(&fn->node);
+ free(fn);
+ }
+}
+
+/*
+ * If the filter has a command line parser and options is not NULL, run it.
+ * Returns filter_num on success, negative on errors
+ */
+static int parse_filter_args(int filter_num, char *options, void **conf)
+{
+ struct filter *f = &filters[filter_num];
+ int i, argc = 2;
+ char *dummy_args[] = {"", "", NULL};
+ char **argv;
+
+// PARA_DEBUG_LOG("%s, options: %s, parser: %p\n", f->name,
+// options? options : "(none)", f->parse_config);
+ if (!f->parse_config)
+ return options? -E_BAD_FILTER_OPTIONS : filter_num;
+ if (options) {
+// PARA_DEBUG_LOG("options: %s\n", options);
+ argc = split_args(options, &argv, ':');
+// PARA_DEBUG_LOG("argc = %d, argv[0]: %s\n", argc, argv[0]);
+ for (i = argc; i >= 0; i--)
+ argv[i + 1] = argv[i];
+ argc += 2;
+ *conf = f->parse_config(argc, argv);
+ } else {
+ /* is it OK to have no options? */
+ *conf = f->parse_config(2, dummy_args);
+ }
+ return *conf? filter_num : -E_BAD_FILTER_OPTIONS;
+}
+
+/**
+ * check the filter command line options
+ *
+ * \param fa the command line options (values separated by colons)
+ * \param conf points to the filter configuration upon successful return
+ *
+ * Check if \a fa starts with a the name of a supported filter, followed by
+ * a colon. If yes, call the command line parser of that filter.
+ *
+ * \return On success, the number of the filter is returned and \a conf
+ * is initialized to point to the filter configuration determined by \a fa.
+ * On errors, a negative value is returned.
+ *
+ * Note: If \a fa specifies a filter that has no command line parser success is
+ * returned, and \a conf is initialized to \p NULL.
+ *
+ * \sa filter::parse_config
+ */
+int check_filter_arg(char *fa, void **conf)
+{
+ int j;
+
+ *conf = NULL;
+// PARA_DEBUG_LOG("arg: %s\n", fa);
+ for (j = 0; filters[j].name; j++) {
+ const char *name = filters[j].name;
+ size_t len = strlen(name);
+ char c;
+ if (strlen(fa) < len)
+ continue;
+ if (strncmp(name, fa, len))
+ continue;
+ c = fa[len];
+ if (c && c != ':')
+ continue;
+ if (c && !filters[j].parse_config)
+ return -E_BAD_FILTER_OPTIONS;
+ return parse_filter_args(j, c? fa + len + 1 : NULL, conf);
+ }
+ return -E_UNSUPPORTED_FILTER;
+}
+
--- /dev/null
+#if __GNUC__ >= 3
+# define inline inline __attribute__ ((always_inline))
+# define __pure __attribute__ ((pure))
+# define __noreturn __attribute__ ((noreturn))
+# define __malloc __attribute__ ((malloc))
+# define __used __attribute__ ((used))
+# define __unused __attribute__ ((unused))
+# define __packed __attribute__ ((packed))
+# define likely(x) __builtin_expect (!!(x), 1)
+# define unlikely(x) __builtin_expect (!!(x), 0)
+/*
+ * p is the number of the "format string" parameter, and q is
+ * the number of the first variadic parameter
+ */
+# define __printf(p,q) __attribute__ ((format (printf, p, q)))
+/*
+ * as direct use of __printf(p,q) confuses doxygen, here are two extra macros
+ * for those values p,q that are actually used by paraslash.
+ */
+#define __printf_1_2 __printf(1,2)
+#define __printf_2_3 __printf(2,3)
+
+#else
+
+# define inline /* no inline */
+# define __pure /* no pure */
+# define __noreturn /* no noreturn */
+# define __malloc /* no malloc */
+# define __used /* no used */
+# define __unused /* no unused */
+# define __packed /* no packed */
+# define likely(x) (x)
+# define unlikely(x) (x)
+# define __printf(p,q) /* no format */
+#define __printf_1_2
+#define __printf_2_3
+#endif
+
+# if __GNUC__ >=3 && __GNUC_MINOR__ > 3
+# define __must_check __attribute__ ((warn_unused_result))
+# else
+# define __must_check /* no warn_unused_result */
+# endif
--- /dev/null
+/*
+ * Copyright (C) 2006 Andre Noll <maan@systemlinux.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+ */
+
+/**
+ * \file grab_client.c functions for grabbing the stream at any position
+ * in a filter chain
+ *
+ * \sa filter_chain filter_chain_info filter
+ */
+
+#include "gcc-compat.h"
+#include "para.h"
+#include "close_on_fork.h"
+#include "grab_client.cmdline.h"
+#include "list.h"
+#include "filter.h"
+#include "grab_client.h"
+#include "audiod.h"
+#include "error.h"
+#include "string.h"
+
+
+/** this maps the enum to the text used at the command line */
+static const char *gc_modes[] = {
+ [GRAB_PEDANTIC] = "pedantic",
+ [GRAB_SLOPPY] = "sloppy",
+ [GRAB_AGGRESSIVE] = "aggressive",
+ NULL
+};
+
+/** grab clients that are not yet attached to a filter node */
+struct list_head inactive_grab_client_list;
+
+static int max_num_filters(void)
+{
+ int i, ret = 0;
+ for (i = 0; audio_formats[i]; i++) {
+ PARA_INFO_LOG("%s filter chain length: %d\n", audio_formats[i],
+ num_filters(i));
+ ret = MAX(ret, num_filters(i));
+ }
+ PARA_INFO_LOG("maximal filter chain length: %d\n", ret);
+ return ret;
+}
+
+static int gc_write(char *buf, size_t len, struct filter_callback *fcb)
+{
+ struct grab_client *gc = fcb->data;
+ struct timeval tv = {0, 100};
+ int ret;
+
+// PARA_INFO_LOG("writing %d bytes to fd %d\n", len, gc->fd);
+ fd_set wfds;
+ do {
+ FD_ZERO(&wfds);
+ FD_SET(gc->fd, &wfds);
+ ret = select(gc->fd + 1, NULL, &wfds, NULL, &tv);
+ } while (ret == EAGAIN || ret == EINTR);
+ if (ret != 1) {
+ if (gc->mode == GRAB_PEDANTIC)
+ return -E_PEDANTIC_GRAB;
+ if (gc->mode == GRAB_SLOPPY)
+ return 1;
+ }
+rewrite:
+ ret = write(gc->fd, buf, len);
+ if (ret < 0) {
+ ret = -E_GC_WRITE;
+ gc->error = E_GC_WRITE;
+ } else {
+ if (ret != len) {
+ if (gc->mode == GRAB_PEDANTIC)
+ return -E_PEDANTIC_GRAB;
+ if (gc->mode == GRAB_AGGRESSIVE) {
+ len -= ret;
+ memmove(buf, buf + ret, len);
+ goto rewrite;
+ }
+ }
+ }
+ return ret;
+}
+
+static int check_gc_args(struct grab_client *gc)
+{
+ int i;
+ struct grab_client_args_info *conf = gc->conf;
+
+ PARA_INFO_LOG("filter_num: %d\n", gc->conf->filter_num_arg);
+ for (i = 0; gc_modes[i]; i++)
+ if (!strcmp(conf->mode_arg, gc_modes[i]))
+ break;
+ if (!gc_modes[i])
+ return -E_INVALID_GRAB_MODE;
+ gc->mode = i;
+ if (conf->audio_format_given) {
+ gc->audio_format_num = get_audio_format_num(conf->audio_format_arg);
+ if (gc->audio_format_num < 0)
+ return gc->audio_format_num;
+ }
+ if (conf->slot_arg > MAX_STREAM_SLOTS)
+ return -E_BAD_GC_SLOT;
+ if (conf->filter_num_arg <= 0)
+ return -E_BAD_GC_FILTER_NUM;
+ if (conf->audio_format_given) {
+ if (num_filters(gc->audio_format_num) < conf->filter_num_arg)
+ return -E_BAD_GC_FILTER_NUM;
+ } else
+ if (conf->filter_num_arg > max_num_filters())
+ return -E_BAD_GC_FILTER_NUM;
+
+ return 1;
+}
+
+static void add_inactive_gc(struct grab_client *gc)
+{
+ PARA_INFO_LOG("adding grab client %p (fd %d) to inactive list\n",
+ gc, gc->fd);
+ list_add(&gc->node, &inactive_grab_client_list);
+}
+
+static void gc_free(struct grab_client *gc)
+{
+ int i;
+
+ for (i = 0; i < gc->argc; i++)
+ free(gc->argv[i]);
+ free(gc->argv);
+ free(gc->conf);
+ free(gc);
+
+}
+
+static void gc_close(struct filter_callback *fcb)
+{
+ struct grab_client *gc = fcb->data;
+
+ if (gc->conf->one_shot_given || gc->error) {
+ PARA_INFO_LOG("closing fd %d (grab client %p)\n", gc->fd, gc);
+ del_close_on_fork_list(gc->fd);
+ close(gc->fd);
+ gc_free(gc);
+ /* close on fork ?*/
+ return;
+ }
+ add_inactive_gc(gc);
+}
+
+/**
+ * move a grab client from the inactive list to a filter node
+ *
+ * \param gc the grab client to activate
+ * \param fn the filter node \a gc gets attached to
+ *
+ * \sa filter_node::callbacks, inactive_grab_client_list
+ */
+void activate_grab_client(struct grab_client *gc, struct filter_node *fn)
+{
+ PARA_INFO_LOG("activating %p (fd %d, filter node: %p)\n", gc, gc->fd, fn);
+ list_del(&gc->node);
+ list_add(&gc->fcb.node, &fn->callbacks);
+}
+
+/**
+ * activate inactive grab clients if possible
+ *
+ * \param slot audiod's slot for the new audio file
+ * \param audio_format_num the number of the audio format of the new audio file
+ * \param filter_list the list of activated filters for that new audio file
+ *
+ * This is called from audiod.c when the current audio file changes. It loops
+ * over all inactive grab clients and checks each grab client's configuration
+ * to determine if the client in question wishes to grab the new stream. If
+ * yes, this grab client is moved from the inactive grab client list to an
+ * appropriate filter_node.
+ *
+ * \sa filter_chain_info::filters, inactive_grab_client_list,
+ * activate_grab_client
+ */
+void activate_inactive_grab_clients(int slot, int audio_format_num,
+ struct list_head *filter_list)
+{
+ struct grab_client *gc, *tmp;
+ int i;
+ struct filter_node *fn;
+
+ list_for_each_entry_safe(gc, tmp, &inactive_grab_client_list, node) {
+// PARA_INFO_LOG("checking inactive grab client %p\n", gc);
+ if (gc->conf->slot_arg >= 0 && gc->conf->slot_arg != slot)
+ continue;
+ if (gc->audio_format_num >= 0 && gc->audio_format_num !=
+ audio_format_num)
+ continue;
+ if (gc->conf->filter_num_arg >= 0 &&
+ num_filters(gc->audio_format_num)
+ < gc->conf->filter_num_arg)
+ continue;
+ i = 1;
+ list_for_each_entry(fn, filter_list, node) {
+ if (gc->conf->filter_num_arg <= 0
+ || i == gc->conf->filter_num_arg)
+ break;
+ i++;
+ }
+ activate_grab_client(gc, fn);
+ }
+}
+
+/**
+ * check the command line options and allocate a grab_client structure
+ *
+ * \param fd the file descriptor of the client
+ * \param argc the number of command line options
+ * \param argv pointers to the command line options
+ * \param err non-zero if an error occured
+ *
+ * If the command line options given by \a argc and \a argv are valid.
+ * allocate a struct grab_client and initialize it with this valid
+ * configuration. Moreover, add the new grab client to the inactive list.
+ *
+ * \return On success, this function returns a pointer to the newly created
+ * struct. On errors, it returns NULL and sets \a err appropriately.
+ *
+ * \sa grab_client, inactive_grab_client_list, activate_grab_client,
+ * filter_node::callbacks
+ */
+/*
+ * argc, argv get freed when com_grab() returns, so we have to make a
+ * copy.
+ */
+__malloc struct grab_client *grab_client_new(int fd, int argc, char **argv, int *err)
+{
+ int i, ret;
+ struct grab_client *gc = para_calloc(sizeof(struct grab_client));
+
+ gc->conf = para_calloc(sizeof(struct grab_client_args_info));
+ gc->argc = argc;
+ gc->argv = para_calloc((argc + 1) * sizeof(char *));
+
+ for (i = 0; i < argc; i++) {
+ gc->argv[i] = para_strdup(argv[i]);
+ PARA_INFO_LOG("argc: %d, argv[%d]: %s\n", argc, i, gc->argv[i]);
+ }
+ PARA_INFO_LOG("argv[%d]: %s\n", argc, gc->argv[argc]);
+ ret = grab_client_cmdline_parser(gc->argc, gc->argv , gc->conf);
+ *err = -E_GC_SYNTAX;
+ if (ret)
+ goto err_out;
+ *err = -E_GC_HELP_GIVEN;
+ if (gc->conf->help_given)
+ goto err_out;
+ *err = check_gc_args(gc);
+ if (*err < 0)
+ goto err_out;
+ if (gc->conf->input_grab_given) {
+ gc->fcb.input_cb = gc_write;
+ gc->fcb.output_cb = NULL;
+ } else {
+ gc->fcb.output_cb = gc_write;
+ gc->fcb.input_cb = NULL;
+ }
+ gc->fd = fd;
+ gc->fcb.close = gc_close;
+ gc->fcb.data = gc;
+ add_inactive_gc(gc);
+ return gc;
+err_out:
+ for (i = 0; i < argc; i++)
+ free(gc->argv[i]);
+ free(gc->argv);
+ free(gc->conf);
+ free(gc);
+ return NULL;
+}
+
+/** initialize the grabbing subsystem.
+ *
+ * This has to be called once during startup before any other function from
+ * grab_client.c may be used. It initializes \a inactive_grab_client_list.
+ */
+void init_grabbing()
+{
+ PARA_INFO_LOG("%s", "grab init\n");
+ INIT_LIST_HEAD(&inactive_grab_client_list);
+}
+
--- /dev/null
+option "filter_num" f "point of filter chain to grab" int typestr="num" default="1" no
+option "slot" s "only grab this slot; grab any slot if negative" int typestr="num" default="-1" no
+option "mode" m "sloppy, pedantic, or aggressive" string typestr="grab_mode" default="sloppy" no
+option "audio_format" a "only grab this type of input stream; grab any if empty" string typestr="name" default="" no
+option "input_grab" i "grab the filter input instead of its output" flag off no
+option "one_shot" o "stop grabbing if audio file changes" flag off no
--- /dev/null
+/*
+ * Copyright (C) 2006 Andre Noll <maan@systemlinux.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+ */
+
+/** \file grab_client.h exported symbols from grab_client.c */
+
+#include "config.h"
+/**
+ * handle blocking writes for the grab client fds
+ *
+ * - pedantic: close fd if write would block
+ * - sloppy: ignore the data and do not write
+ * - aggressive: write anyway (default)
+ *
+ */
+enum grab_mode {GRAB_PEDANTIC, GRAB_SLOPPY, GRAB_AGGRESSIVE};
+
+/** describes one active grab client
+ *
+ * \sa filter_callback, filter_node::callbacks
+ */
+struct grab_client {
+/** the file descriptor to send the grabbed stream to */
+ int fd;
+/** the command line options for this grab client */
+ struct grab_client_args_info *conf;
+/** pedantic, sloppy, or aggressive, computed from command line */
+ enum grab_mode mode;
+/** non-zero if the write() to \a fd failed */
+ int error;
+/** the number of the desired audio format, computed from command line */
+ int audio_format_num;
+/** the callback data which gets attached to a suitable filter_node */
+ struct filter_callback fcb;
+/** all grab clients belong either to a filter node or to the inactive list */
+ struct list_head node;
+/** the number of command line options */
+ int argc;
+/** pointers to the command line options */
+ char **argv;
+};
+
+__malloc struct grab_client *grab_client_new(int fd, int argc, char **argv, int *err);
+void activate_inactive_grab_clients(int slot, int audio_format_num, struct list_head *filter_list);
+void activate_grab_client(struct grab_client *gc, struct filter_node *fn);
+void init_grabbing(void);
--- /dev/null
+/*
+ * Copyright (C) 1998-2006 Andre Noll <maan@systemlinux.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+ */
+
+/** \file gui.c ncurses-based interface for paraslash */
+
+#include "gui.cmdline.h"
+#include "para.h"
+#include "gcc-compat.h"
+#include <curses.h>
+#include "ringbuffer.h"
+#include "string.h"
+
+extern const char *status_item_list[NUM_STAT_ITEMS];
+static char *stat_content[NUM_STAT_ITEMS];
+
+#define STANDARD_STATUS_BAR "para_gui " VERSION " (hit ? for help)"
+
+static int signal_pipe;
+
+static void finish(int sig);
+
+static struct win_data {
+ WINDOW *win;
+ NCURSES_SIZE_T begx;
+ NCURSES_SIZE_T begy;
+ NCURSES_SIZE_T cols;
+ NCURSES_SIZE_T lines;
+} top, bot, sb, in, sep;
+
+#define RINGBUFFER_SIZE 512
+struct rb_entry {
+ char *msg;
+ size_t len;
+ int color;
+};
+void *bot_win_rb;
+#define NUM_LINES(len) (1 + (len) / bot.cols)
+
+static unsigned scroll_position;
+
+static int external_cmd_died, curses_active;
+static pid_t external_cmd_pid;
+
+static int command_pipe = -1;
+static int audiod_pipe = -1;
+static struct gengetopt_args_info conf;
+
+enum {GETCH_MODE, COMMAND_MODE, EXTERNAL_MODE};
+
+
+#define COLOR_STATUSBAR 32
+#define COLOR_COMMAND 33
+#define COLOR_OUTPUT 34
+#define COLOR_MSG 35
+#define COLOR_ERRMSG 36
+#define COLOR_WELCOME 37
+#define COLOR_SEPARATOR 38
+#define COLOR_TOP 39
+#define COLOR_BOT 40
+
+struct gui_command {
+ char *key;
+ char *name;
+ char *description;
+ void (*handler)(void);
+};
+
+struct stat_item {
+ char name[MAXLINE];
+ char prefix[MAXLINE];
+ char postfix[MAXLINE];
+ unsigned y;
+ unsigned x;
+ unsigned len;
+ int fg, bg;
+ int align;
+ char content[MAXLINE];
+};
+
+static struct gui_theme theme;
+
+int _argc;
+char **_argv;
+
+static void com_help(void);
+static void com_reread_conf(void);
+static void com_enlarge_top_win(void);
+static void com_shrink_top_win(void);
+static void com_version(void);
+static void com_quit(void);
+static void com_refresh(void);
+static void com_ll_incr(void);
+static void com_ll_decr(void);
+static void com_prev_theme(void);
+static void com_next_theme(void);
+static void com_scroll_up(void);
+static void com_scroll_down(void);
+static void com_page_up(void);
+static void com_page_down(void);
+
+struct gui_command command_list[] = {
+ {
+ .key = "?",
+ .name = "help",
+ .description = "print help",
+ .handler = com_help
+ }, {
+ .key = "+",
+ .name = "enlarge_win",
+ .description = "enlarge the top window",
+ .handler = com_enlarge_top_win
+ }, {
+ .key = "-",
+ .name = "shrink_win",
+ .description = "shrink the top window",
+ .handler = com_shrink_top_win
+ }, {
+ .key = "r",
+ .name = "reread_conf",
+ .description = "reread configuration file",
+ .handler = com_reread_conf
+ }, {
+ .key = "q",
+ .name = "quit",
+ .description = "exit para_gui",
+ .handler = com_quit
+ }, {
+ .key = "^L",
+ .name = "refresh",
+ .description = "redraw the screen",
+ .handler = com_refresh
+ }, {
+ .key = ".",
+ .name = "next_theme",
+ .description = "switch to next theme",
+ .handler = com_next_theme
+ }, {
+ .key = ",",
+ .name = "prev_theme",
+ .description = "switch to previous stream",
+ .handler = com_prev_theme
+ }, {
+ .key = ">",
+ .name = "ll_incr",
+ .description = "increase loglevel (decreases verbosity)",
+ .handler = com_ll_incr
+ }, {
+ .key = "<",
+ .name = "ll_decr",
+ .description = "decrease loglevel (increases verbosity)",
+ .handler = com_ll_decr
+ }, {
+ .key = "V",
+ .name = "version",
+ .description = "show the para_gui version",
+ .handler = com_version
+ }, {
+ .key = "<up>",
+ .name = "scroll_up",
+ .description = "scroll up one line",
+ .handler = com_scroll_up
+ }, {
+ .key = "<down>",
+ .name = "scroll_down",
+ .description = "scroll down one line",
+ .handler = com_scroll_down
+ }, {
+ .key = "<ppage>",
+ .name = "page_up",
+ .description = "scroll up one page",
+ .handler = com_page_up
+ }, {
+ .key = "<npage>",
+ .name = "page_down",
+ .description = "scroll down one page",
+ .handler = com_page_down
+ }, {
+ .handler = NULL
+ }
+};
+
+static int find_cmd_byname(char *name)
+{
+ int i;
+
+ for (i = 0; command_list[i].handler; i++)
+ if (!strcmp(command_list[i].name, name))
+ return i;
+ return -1;
+}
+
+/* taken from mutt */
+static char *km_keyname(int c)
+{
+ static char buf[10];
+
+ if (c == KEY_UP) {
+ sprintf(buf, "<up>");
+ return buf;
+ }
+ if (c == KEY_DOWN) {
+ sprintf(buf, "<down>");
+ return buf;
+ }
+ if (c == KEY_LEFT) {
+ sprintf(buf, "<left>");
+ return buf;
+ }
+ if (c == KEY_RIGHT) {
+ sprintf(buf, "<right>");
+ return buf;
+ }
+ if (c == KEY_NPAGE) {
+ sprintf(buf, "<npage>");
+ return buf;
+ }
+ if (c == KEY_PPAGE) {
+ sprintf(buf, "<ppage>");
+ return buf;
+ }
+ if (c < 256 && c > -128 && iscntrl((unsigned char) c)) {
+ if (c < 0)
+ c += 256;
+ if (c < 128) {
+ buf[0] = '^';
+ buf[1] = (c + '@') & 0x7f;
+ buf[2] = 0;
+ } else
+ snprintf(buf, sizeof(buf), "\\%d%d%d", c >> 6,
+ (c >> 3) & 7, c & 7);
+ } else if (c >= KEY_F0 && c < KEY_F(256))
+ sprintf(buf, "<F%d>", c - KEY_F0);
+ else if (isprint(c))
+ snprintf(buf, sizeof(buf), "%c", (unsigned char) c);
+ else
+ snprintf(buf, sizeof(buf), "\\x%hx", (unsigned short) c);
+ return buf;
+}
+
+static char *configfile_exists(void)
+{
+ static char *config_file;
+ char *tmp;
+
+ if (!conf.config_file_given) {
+ if (!config_file) {
+ char *home = para_homedir();
+ config_file = make_message("%s/.paraslash/gui.conf",
+ home);
+ free(home);
+ }
+ tmp = config_file;
+ } else
+ tmp = conf.config_file_arg;
+ return file_exists(tmp)? tmp: NULL;
+}
+
+/*
+ * print num spaces to curses window
+ */
+static void add_spaces(WINDOW* win, unsigned int num)
+{
+ while (num > 0) {
+ num--;
+ waddstr(win, " ");
+ }
+}
+
+/*
+ * print aligned string to curses window. This function always prints
+ * exactly len chars.
+ */
+static int align_str(WINDOW* win, char *string, unsigned int len,
+ unsigned int align)
+{
+ int num; /* of spaces */
+ char *str;
+
+ if (!win || !string)
+ return -1;
+ num = len - strlen(string);
+ str = para_strdup(string);
+ if (num < 0) {
+ str[len] = '\0';
+ num = 0;
+ }
+ if (align == LEFT) {
+ waddstr(win, str);
+ add_spaces(win, num);
+ } else if (align == RIGHT) {
+ add_spaces(win, num);
+ waddstr(win, str);
+ } else {
+ add_spaces(win, num / 2);
+ waddstr(win, str[0]? str: "");
+ add_spaces(win, num - num / 2);
+ }
+ free(str);
+ return 1;
+}
+
+__printf_2_3 static void print_in_bar(int color, char *fmt,...)
+{
+ char *msg;
+
+ if (!curses_active)
+ return;
+ wattron(in.win, COLOR_PAIR(color));
+ PARA_VSPRINTF(fmt, msg);
+ wmove(in.win, 0, 0);
+ align_str(in.win, msg, sb.cols, LEFT);
+ free(msg);
+ wrefresh(in.win);
+}
+
+/*
+ * update the status bar
+ */
+static void print_status_bar(void)
+{
+ if (!curses_active)
+ return;
+ wmove(sb.win, 0, 0);
+ align_str(sb.win,STANDARD_STATUS_BAR, sb.cols, CENTER);
+ wrefresh(sb.win);
+}
+
+/*
+ * get the number of the oldest rbe that is (partially) visible. On return,
+ * lines contains the sum of the number of lines of all visable entries. If the
+ * first one is only partially visible, lines is greater than bot.lines.
+ */
+static int first_visible_rbe(unsigned *lines)
+{
+ int i;
+ *lines = 0;
+ for (i = scroll_position; i < RINGBUFFER_SIZE; i++) {
+ struct rb_entry *rbe = ringbuffer_get(bot_win_rb, i);
+ int rbe_lines;
+ if (!rbe)
+ return i - 1;
+// fprintf(stderr, "found: %s\n", rbe->msg);
+ rbe_lines = NUM_LINES(rbe->len);
+ if (rbe_lines > bot.lines)
+ return -1;
+ *lines += rbe_lines;
+ if (*lines >= bot.lines)
+ return i;
+ }
+ return RINGBUFFER_SIZE - 1;
+}
+
+static int draw_top_rbe(unsigned *lines)
+{
+ unsigned len;
+ int offset, fvr = first_visible_rbe(lines);
+ struct rb_entry *rbe;
+
+ if (fvr < 0)
+ return -1;
+ wmove(bot.win, 0, 0);
+ rbe = ringbuffer_get(bot_win_rb, fvr);
+ if (!rbe)
+ return -1;
+ /* first rbe might be only partially visible */
+ offset = (*lines - bot.lines) * bot.cols;
+ len = strlen(rbe->msg);
+ if (offset < 0 || len < offset)
+ return -1;
+ wattron(bot.win, COLOR_PAIR(rbe->color));
+ waddstr(bot.win, rbe->msg + offset);
+ *lines = NUM_LINES(len - offset);
+ return fvr;
+}
+
+static void redraw_bot_win(void)
+{
+ unsigned lines;
+ int i;
+
+ wmove(bot.win, 0, 0);
+ wclear(bot.win);
+ i = draw_top_rbe(&lines);
+ if (i <= 0)
+ goto out;
+ while (i > 0 && lines < bot.lines) {
+ struct rb_entry *rbe = ringbuffer_get(bot_win_rb, --i);
+ if (!rbe) {
+ lines++;
+ waddstr(bot.win, "\n");
+ continue;
+ }
+ lines += NUM_LINES(rbe->len);
+ wattron(bot.win, COLOR_PAIR(rbe->color));
+ waddstr(bot.win, "\n");
+ waddstr(bot.win, rbe->msg);
+ }
+out:
+ wrefresh(bot.win);
+}
+
+static void rb_add_entry(int color, char *msg)
+{
+ struct rb_entry *old, *new = para_malloc(sizeof(struct rb_entry));
+ int x, y;
+ new->color = color;
+ new->len = strlen(msg);
+ new->msg = msg;
+ old = ringbuffer_add(bot_win_rb, new);
+// fprintf(stderr, "added: %s\n", new->msg);
+ if (old) {
+ free(old->msg);
+ free(old);
+ }
+ if (scroll_position) {
+ /* discard current scrolling, like xterm does */
+ scroll_position = 0;
+ redraw_bot_win();
+ return;
+ }
+ wattron(bot.win, COLOR_PAIR(color));
+ getyx(bot.win, y, x);
+ if (y || x)
+ waddstr(bot.win, "\n");
+ waddstr(bot.win, msg);
+}
+
+/*
+ * print formated output to bot win and refresh
+ */
+__printf_2_3 static void outputf(int color, char* fmt,...)
+{
+ char *msg;
+
+ if (!curses_active)
+ return;
+ PARA_VSPRINTF(fmt, msg);
+ rb_add_entry(color, msg);
+ wrefresh(bot.win);
+}
+
+static void add_output_line(char *line)
+{
+ if (!curses_active)
+ return;
+ rb_add_entry(COLOR_OUTPUT, para_strdup(line));
+}
+
+void para_log(int ll, char *fmt,...)
+{
+ int color;
+ char *msg;
+
+ if (ll < conf.loglevel_arg || !curses_active)
+ return;
+ switch (ll) {
+ case DEBUG:
+ case INFO:
+ case NOTICE:
+ color = COLOR_MSG;
+ break;
+ default:
+ color = COLOR_ERRMSG;
+ }
+ PARA_VSPRINTF(fmt, msg);
+ chop(msg);
+ rb_add_entry(color, msg);
+ wrefresh(bot.win);
+}
+
+static void setup_signal_handling(void)
+{
+ signal_pipe = para_signal_init();
+ para_install_sighandler(SIGINT);
+ para_install_sighandler(SIGTERM);
+ para_install_sighandler(SIGCHLD);
+ para_install_sighandler(SIGWINCH);
+ para_install_sighandler(SIGUSR1);
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGHUP, SIG_IGN);
+}
+
+static void do_exit(int ret)
+{
+ signal(SIGTERM, SIG_IGN);
+ kill(0, SIGTERM);
+ exit(ret);
+}
+
+static void shutdown_curses(void)
+{
+ if (!curses_active)
+ return;
+ def_prog_mode();
+ curses_active = 0;
+ endwin();
+}
+
+static void finish(int ret)
+{
+ shutdown_curses();
+ do_exit(ret);
+}
+
+/*
+ * exit curses and print given message to stdout/stderr
+ */
+__printf_2_3 static void msg_n_exit(int ret, char* fmt, ...)
+{
+ va_list argp;
+ FILE *outfd = ret? stderr: stdout;
+
+ shutdown_curses();
+ va_start(argp, fmt);
+ vfprintf(outfd, fmt, argp);
+ va_end(argp);
+ do_exit(ret);
+}
+
+static void print_welcome(void)
+{
+ int ll = conf.loglevel_arg;
+ if (ll > NOTICE)
+ return;
+ outputf(COLOR_WELCOME, "Welcome to para_gui " VERSION
+ " \"" CODENAME "\". Theme: %s", theme.name);
+ wclrtoeol(bot.win);
+}
+
+/*
+ * init all windows
+ */
+static void init_wins(int top_lines)
+{
+ int i;
+
+ top.lines = top_lines;
+ top.cols = COLS;
+ top.begy = 0;
+ top.begx = 0;
+
+ bot.lines = LINES - top.lines - 3;
+ bot.cols = COLS;
+ bot.begy = top.lines + 1;
+ bot.begx = 0;
+
+ sb.lines = 1;
+ sb.cols = COLS;
+ sb.begy = LINES - 2;
+ sb.begx = 0;
+
+ in.lines = 1;
+ in.cols = COLS;
+ in.begy = LINES - 1;
+ in.begx = 0;
+
+ sep.lines = 1;
+ sep.cols = COLS;
+ sep.begy = top.lines;
+ sep.begx = 0;
+
+ assume_default_colors(theme.default_fg, theme.default_bg);
+ if (top.win) {
+ mvwin(top.win, top.begy, top.begx);
+ wresize(top.win, top.lines, top.cols);
+
+ mvwin(sb.win, sb.begy, sb.begx);
+ wresize(sb.win, sb.lines, sb.cols);
+
+ mvwin(sep.win, sep.begy, sep.begx);
+ wresize(sep.win, sep.lines, sep.cols);
+
+ mvwin(bot.win, bot.begy, bot.begx);
+ wresize(bot.win, bot.lines, bot.cols);
+
+ mvwin(in.win, in.begy, in.begx);
+ wresize(in.win, in.lines, in.cols);
+ } else {
+ sep.win = newwin(sep.lines, sep.cols, sep.begy, sep.begx);
+ top.win = newwin(top.lines, top.cols, top.begy, top.begx);
+ bot.win = newwin(bot.lines, bot.cols, bot.begy, bot.begx);
+ sb.win = newwin(sb.lines, sb.cols, sb.begy, sb.begx);
+ in.win = newwin(in.lines, in.cols, in.begy, in.begx);
+ if (!top.win || !bot.win || !sb.win || !in.win || !sep.win)
+ msg_n_exit(1, "Error: Cannot create curses windows\n");
+ wclear(bot.win);
+ wclear(sb.win);
+ wclear(in.win);
+ scrollok(bot.win, 1);
+ wattron(sb.win, COLOR_PAIR(COLOR_STATUSBAR));
+ wattron(sep.win, COLOR_PAIR(COLOR_SEPARATOR));
+ wattron(bot.win, COLOR_PAIR(COLOR_BOT));
+ wattron(top.win, COLOR_PAIR(COLOR_TOP));
+ nodelay(top.win, 1);
+ nodelay(bot.win, 1);
+ nodelay(sb.win, 1);
+ nodelay(in.win, 0);
+
+ keypad(top.win, 1);
+ keypad(bot.win, 1);
+ keypad(sb.win, 1);
+ keypad(in.win, 1);
+ print_status_bar();
+ }
+ wmove(sep.win, 0, 0);
+ for (i = 1; i <= COLS; i++)
+ waddstr(sep.win, theme.sep_str);
+ wclear(top.win);
+ //wclear(bot.win);
+ wnoutrefresh(top.win);
+ wnoutrefresh(bot.win);
+ //wnoutrefresh(sb.win);
+ print_status_bar();
+ wnoutrefresh(in.win);
+ wnoutrefresh(sep.win);
+ doupdate();
+}
+
+static void check_geometry(void)
+{
+ if (LINES < theme.lines_min || COLS < theme.cols_min)
+ msg_n_exit(EXIT_FAILURE, "Error: Terminal (%dx%d) too small"
+ " (need at least %dx%d)\n", COLS, LINES,
+ theme.cols_min, theme.lines_min);
+}
+
+/*
+ * Print stat item #i to curses window
+ */
+static void print_stat_item(int i)
+{
+ char *tmp;
+ struct stat_item_data d = theme.data[i];
+ char *c = stat_content[i];
+
+ if (!curses_active || !d.len || !c)
+ return;
+ tmp = make_message("%s%s%s", d.prefix, c, d.postfix);
+// PARA_DEBUG_LOG("%s: read: %s\n", __func__, tmp);
+ wmove(top.win, d.y * top.lines / 100, d.x * COLS / 100);
+ wrefresh(top.win);
+ wattron(top.win, COLOR_PAIR(i + 1));
+ align_str(top.win, tmp, d.len * COLS / 100, d.align);
+ free(tmp);
+ wrefresh(top.win);
+}
+
+static void print_all_items(void)
+{
+ int i;
+
+ if (!curses_active)
+ return;
+ for (i = 0; i < NUM_STAT_ITEMS; i++)
+ print_stat_item(i);
+}
+static void clear_all_items(void)
+{
+ int i;
+
+ for (i = 0; i < NUM_STAT_ITEMS; i++) {
+ free(stat_content[i]);
+ stat_content[i] = para_strdup("");
+ }
+}
+
+static void init_colors(void)
+{
+ int i;
+ if (!has_colors())
+ msg_n_exit(EXIT_FAILURE, "Error: No color term\n");
+ start_color();
+ for (i = 0; i < NUM_STAT_ITEMS; i++)
+ if (theme.data[i].len)
+ init_pair(i + 1, theme.data[i].fg, theme.data[i].bg);
+ init_pair(COLOR_STATUSBAR, theme.sb_fg, theme.sb_bg);
+ init_pair(COLOR_COMMAND, theme.cmd_fg, theme.cmd_bg);
+ init_pair(COLOR_OUTPUT, theme.output_fg, theme.output_bg);
+ init_pair(COLOR_MSG, theme.msg_fg, theme.msg_bg);
+ init_pair(COLOR_ERRMSG, theme.err_msg_fg, theme.err_msg_bg);
+ init_pair(COLOR_WELCOME, theme.welcome_fg, theme.welcome_bg);
+ init_pair(COLOR_SEPARATOR, theme.sep_fg, theme.sep_bg);
+ init_pair(COLOR_TOP, theme.default_fg, theme.default_bg);
+ init_pair(COLOR_BOT, theme.default_fg, theme.default_bg);
+}
+
+/*
+ * (re-)initialize the curses library FIXME: Error checking
+ */
+static void init_curses(void)
+{
+ curses_active = 1;
+ if (top.win && refresh() == ERR) { /* refesh is really needed */
+ msg_n_exit(EXIT_FAILURE, "refresh() failed\n");
+ }
+ check_geometry();
+ curs_set(0); /* make cursor invisible, ignore errors */
+// if (noraw() == ERR);
+// msg_n_exit(EXIT_FAILURE, "can not place terminal out of "
+// "raw mode\n");
+ nonl(); /* tell curses not to do NL->CR/NL on output */
+ noecho(); /* don't echo input */
+ cbreak(); /* take input chars one at a time, no wait for \n */
+ //reset_prog_mode();
+ init_colors();
+ clear();
+ init_wins(theme.top_lines_default);
+ print_all_items();
+ noecho(); /* don't echo input */
+}
+
+
+static void check_sigchld(void)
+{
+ pid_t pid;
+reap_next_child:
+ pid = para_reap_child();
+ if (pid <= 0)
+ return;
+ if (pid == external_cmd_pid) {
+ external_cmd_pid = 0;
+ external_cmd_died = 1;
+ }
+ goto reap_next_child;
+}
+
+/*
+ * print status line if line starts with known command.
+ */
+static void check_stat_line(char *line)
+{
+ int i;
+
+// PARA_INFO_LOG("%s: checking: %s\n", __func__, line);
+ i = stat_line_valid(line);
+ if (i >= 0) {
+ line += strlen(status_item_list[i]) + 1;
+ free(stat_content[i]);
+ stat_content[i] = para_strdup(line);
+ print_stat_item(i);
+ }
+ return;
+}
+
+/*
+ * This sucker modifies its first argument. *handler and *arg are
+ * pointers to 0-terminated strings (inside line). Crap.
+ */
+static int split_key_map(char *line, char **handler, char **arg)
+{
+ if (!(*handler = strchr(line + 1, ':')))
+ goto err_out;
+ **handler = '\0';
+ (*handler)++;
+ if (!(*arg = strchr(*handler, ':')))
+ goto err_out;
+ **arg = '\0';
+ (*arg)++;
+ return 1;
+err_out:
+ return 0;
+}
+
+static int check_key_map_args(void)
+{
+ char *s;
+ int i, ret = -1;
+ char *tmp = NULL, *handler, *arg;
+
+ for (i = 0; i < conf.key_map_given; ++i) {
+ s = conf.key_map_arg[i];
+ if (!(*s))
+ goto err_out;
+ free(tmp);
+ tmp = para_strdup(s);
+ if (!split_key_map(tmp, &handler, &arg))
+ goto err_out;
+ if (strlen(handler) != 1)
+ goto err_out;
+ if (*handler != 'x'
+ && *handler != 'd'
+ && *handler != 'i'
+ && *handler != 'p')
+ goto err_out;
+ if (*handler != 'i')
+ continue;
+ if (find_cmd_byname(arg) < 0)
+ goto err_out;
+ }
+ ret = 0;
+err_out:
+ free(tmp);
+ return ret;
+}
+
+/*
+ * React to various signal-related events
+ */
+static void handle_signal(int sig)
+{
+ switch (sig) {
+ case SIGTERM:
+ msg_n_exit(EXIT_FAILURE,
+ "only the good die young (caught SIGTERM))\n");
+ return;
+ case SIGWINCH:
+ if (curses_active) {
+ shutdown_curses();
+ init_curses();
+ redraw_bot_win();
+ }
+ return;
+ case SIGINT:
+ PARA_WARNING_LOG("%s", "caught SIGINT, reset");
+ /* Nothing to do. SIGINT killed our child, para_client stat.
+ * This get noticed by do_select which resets everything
+ */
+ return;
+ case SIGUSR1:
+ PARA_NOTICE_LOG("%s", "got SIGUSR1, rereading configuration");
+ com_reread_conf();
+ return;
+ case SIGCHLD:
+ check_sigchld();
+ return;
+ }
+}
+
+/* open pipe if last attempt to open was more than 2 secs ago */
+static int open_audiod_pipe(void)
+{
+ static time_t open_time, *p;
+ time_t now;
+ double diff;
+
+ if (p) {
+ time(&now);
+ diff = difftime(now, open_time);
+ if (diff < 2)
+ return -1;
+ } else
+ p = &open_time;
+ time(&open_time);
+ return para_open_audiod_pipe(conf.stat_cmd_arg);
+}
+
+
+/*
+ * This is the core select loop. Besides the (internal) signal
+ * pipe, the following other fds are checked according to the mode:
+ *
+ * GETCH_MODE: check stdin, return when key is pressed
+ *
+ * COMMAND_MODE: check command_pipe and stdin. Return when a screen full
+ * of output has been read or when other end has closed command pipe or
+ * when any key is pressed.
+ *
+ * EXTERNAL_MODE: Check only signal pipe. Used when an external command
+ * is running. During that thime curses is disabled. Returns when
+ * external_cmd_pid == 0.
+ */
+static int do_select(int mode)
+{
+ fd_set rfds;
+ int ret;
+ int max_fileno, cp_numread = 1;
+ char command_buf[STRINGSIZE] = "";
+ int cbo = 0; /* command buf offset */
+ struct timeval tv;
+repeat:
+ tv.tv_sec = conf.timeout_arg / 1000;
+ tv.tv_usec = (conf.timeout_arg % 1000) * 1000;
+// ret = refresh_status();
+ FD_ZERO(&rfds);
+ max_fileno = 0;
+ /* audiod pipe */
+ if (audiod_pipe < 0)
+ audiod_pipe = open_audiod_pipe();
+ if (audiod_pipe >= 0) {
+ FD_SET(audiod_pipe, &rfds);
+ max_fileno = MAX(max_fileno, audiod_pipe);
+ }
+
+ /* signal pipe */
+ FD_SET(signal_pipe, &rfds);
+ max_fileno = MAX(max_fileno, signal_pipe);
+ /* command pipe only for COMMAND_MODE */
+ if (command_pipe >= 0 && mode == COMMAND_MODE) {
+ FD_SET(command_pipe, &rfds);
+ max_fileno = MAX(max_fileno, command_pipe);
+ }
+ FD_SET(STDIN_FILENO, &rfds);
+ ret = select(max_fileno + 1, &rfds, NULL, NULL, &tv);
+// PARA_DEBUG_LOG("select returned %d\n", ret);
+
+ /* signals */
+ if (FD_ISSET(signal_pipe, &rfds)) {
+ int sig_nr = para_next_signal();
+ if (sig_nr > 0)
+ handle_signal(sig_nr);
+ }
+ if (ret <= 0)
+ goto check_return; /* skip fd checks */
+ /* read command pipe if ready */
+ if (command_pipe >= 0 && mode == COMMAND_MODE &&
+ FD_ISSET(command_pipe, &rfds)) {
+ cp_numread = read(command_pipe, command_buf + cbo,
+ STRINGSIZE - 1 - cbo);
+ if (cp_numread >= 0)
+ cbo += cp_numread;
+ else {
+ if (cp_numread < 0)
+ PARA_ERROR_LOG("read error (%d)", cp_numread);
+ close(command_pipe);
+ command_pipe = -1;
+ }
+ }
+ if (audiod_pipe >= 0 && FD_ISSET(audiod_pipe, &rfds))
+ if (read_audiod_pipe(audiod_pipe, check_stat_line) <= 0) {
+ close(audiod_pipe);
+ audiod_pipe = -1;
+ clear_all_items();
+ free(stat_content[SI_STATUS_BAR]);
+ stat_content[SI_STATUS_BAR] =
+ para_strdup("audiod not running!?\n");
+ print_all_items();
+ sleep(1);
+ }
+check_return:
+ switch (mode) {
+ case COMMAND_MODE:
+ if (cp_numread <= 0 && !cbo) /* command complete */
+ return 0;
+ if (cbo)
+ cbo = for_each_line(command_buf, cbo, &add_output_line, 0);
+ if (cp_numread <= 0)
+ cbo = 0;
+ wrefresh(bot.win);
+ break;
+ case GETCH_MODE:
+ ret = wgetch(top.win);
+ if (ret != ERR && ret != KEY_RESIZE)
+ return ret;
+ break;
+ case EXTERNAL_MODE:
+ if (external_cmd_died) {
+ external_cmd_died = 0;
+ return 0;
+ }
+ }
+ goto repeat;
+}
+
+/*
+ * read from command pipe and print data to bot window
+ */
+static int send_output(void)
+{
+ if (command_pipe < 0)
+ return 0;
+ if (do_select(COMMAND_MODE) >= 0)
+ PARA_INFO_LOG("%s", "command complete");
+ else
+ PARA_NOTICE_LOG("%s", "command aborted");
+ print_in_bar(COLOR_MSG, " ");
+ return 1;
+}
+
+static int client_cmd_cmdline(char *cmd)
+{
+ pid_t pid;
+ int ret, fds[3] = {0, 1, 0};
+ char *c = make_message(BINDIR "/para_client %s", cmd);
+
+ outputf(COLOR_COMMAND, "%s", c);
+ print_in_bar(COLOR_MSG, "executing client command, hit q to abort\n");
+ ret = para_exec_cmdline_pid(&pid, c, fds);
+ free(c);
+ if (ret < 0)
+ return -1;
+ command_pipe = fds[1];
+ return send_output();
+}
+
+/*
+ * exec command and print output to bot win
+ */
+static int display_cmd(char *cmd)
+{
+ pid_t pid;
+ int fds[3] = {0, 1, 0};
+
+ print_in_bar(COLOR_MSG, "executing display command, hit q to abort");
+ outputf(COLOR_COMMAND, "%s", cmd);
+ if (para_exec_cmdline_pid(&pid, cmd, fds) < 0)
+ return -1;
+ command_pipe = fds[1];
+ return send_output();
+}
+
+/*
+ * shutdown curses and stat pipe before executing external commands
+ */
+static int external_cmd(char *cmd)
+{
+ int fds[3] = {-1, -1, -1};
+
+ if (external_cmd_pid)
+ return -1;
+ shutdown_curses();
+ para_exec_cmdline_pid(&external_cmd_pid, cmd, fds);
+ do_select(EXTERNAL_MODE);
+ init_curses();
+ return 0;
+}
+
+static void print_scroll_msg(void)
+{
+ unsigned lines_total, filled = ringbuffer_filled(bot_win_rb);
+ int first_rbe = first_visible_rbe(&lines_total);
+ print_in_bar(COLOR_MSG, "scrolled view: %d-%d/%d\n", filled - first_rbe,
+ filled - scroll_position, ringbuffer_filled(bot_win_rb));
+}
+
+static void com_page_down(void)
+{
+ unsigned lines = 0;
+ int i = scroll_position;
+ while (lines < bot.lines && --i > 0) {
+ struct rb_entry *rbe = ringbuffer_get(bot_win_rb, i);
+ if (!rbe)
+ break;
+ lines += NUM_LINES(strlen(rbe->msg));
+ }
+ if (lines) {
+ scroll_position = i;
+ redraw_bot_win();
+ print_scroll_msg();
+ return;
+ }
+ print_in_bar(COLOR_ERRMSG, "bottom of buffer is shown\n");
+}
+
+static void com_page_up(void)
+{
+ unsigned lines;
+ int fvr = first_visible_rbe(&lines);
+ if (fvr < 0 || fvr + 1 >= ringbuffer_filled(bot_win_rb)) {
+ print_in_bar(COLOR_ERRMSG, "top of buffer is shown\n");
+ return;
+ }
+ scroll_position = fvr + 1;
+ for (; scroll_position > 0; scroll_position--) {
+ fvr = first_visible_rbe(&lines);
+ if (lines == bot.lines)
+ break;
+ }
+ redraw_bot_win();
+ print_scroll_msg();
+}
+
+static void com_scroll_down(void)
+{
+ struct rb_entry *rbe;
+ int rbe_lines;
+
+ if (!scroll_position) {
+ print_in_bar(COLOR_ERRMSG, "bottom of buffer is shown\n");
+ return;
+ }
+ scroll_position--;
+ rbe = ringbuffer_get(bot_win_rb, scroll_position);
+ rbe_lines = NUM_LINES(rbe->len);
+ wscrl(bot.win, rbe_lines);
+ wmove(bot.win, bot.lines - rbe_lines, 0);
+ wattron(bot.win, COLOR_PAIR(rbe->color));
+ waddstr(bot.win, rbe->msg);
+ wrefresh(bot.win);
+ print_scroll_msg();
+}
+
+static void com_scroll_up(void)
+{
+ struct rb_entry *rbe = NULL;
+ unsigned lines;
+ int i, first_rbe, scroll;
+
+ /* the entry that is going to vanish */
+ rbe = ringbuffer_get(bot_win_rb, scroll_position);
+ if (!rbe)
+ goto err_out;
+ scroll = NUM_LINES(rbe->len);
+ first_rbe = first_visible_rbe(&lines);
+ if (first_rbe < 0 || (first_rbe == ringbuffer_filled(bot_win_rb) - 1))
+ goto err_out;
+ scroll_position++;
+ wscrl(bot.win, -scroll);
+ i = draw_top_rbe(&lines);
+ if (i < 0)
+ goto err_out;
+ while (i > 0 && lines < scroll) {
+ int rbe_lines;
+ rbe = ringbuffer_get(bot_win_rb, --i);
+ if (!rbe)
+ break;
+ rbe_lines = NUM_LINES(rbe->len);
+ lines += rbe_lines;
+// fprintf(stderr, "msg: %s\n", rbe->msg);
+ wattron(bot.win, COLOR_PAIR(rbe->color));
+ waddstr(bot.win, "\n");
+ waddstr(bot.win, rbe->msg);
+ if (!i)
+ break;
+ i--;
+ }
+ wrefresh(bot.win);
+ print_scroll_msg();
+ return;
+err_out:
+ print_in_bar(COLOR_ERRMSG, "top of buffer is shown\n");
+}
+
+static void com_ll_decr(void)
+{
+ if (conf.loglevel_arg <= DEBUG) {
+ print_in_bar(COLOR_ERRMSG,
+ "loglevel already at maximal verbosity\n");
+ return;
+ }
+ conf.loglevel_arg--;
+ print_in_bar(COLOR_MSG, "loglevel set to %d\n", conf.loglevel_arg);
+}
+
+static void com_ll_incr(void)
+{
+ if (conf.loglevel_arg >= EMERG) {
+ print_in_bar(COLOR_ERRMSG,
+ "loglevel already at miminal verbosity\n");
+ return;
+ }
+ conf.loglevel_arg++;
+ print_in_bar(COLOR_MSG, "loglevel set to %d\n", conf.loglevel_arg);
+}
+
+/*
+ * reread configuration, terminate on errors
+ */
+static void com_reread_conf(void)
+{
+ char *cf =configfile_exists();
+
+ if (!cf) {
+ PARA_WARNING_LOG("%s", "there is no configuration to read");
+ return;
+ }
+ PARA_INFO_LOG("%s", "rereading command line options and config file");
+ cmdline_parser(_argc, _argv, &conf);
+ cmdline_parser_configfile(cf, &conf, 1, 1, 0);
+ PARA_NOTICE_LOG("%s", "configuration read");
+ if (check_key_map_args() < 0)
+ finish(EXIT_FAILURE);
+}
+
+static void com_help(void)
+{
+ int i;
+
+ for (i = 0; i < conf.key_map_given; ++i) {
+ char *s = conf.key_map_arg[i], *handler, *arg,
+ *desc = "", tmp[MAXLINE];
+
+ strcpy(tmp, s);
+ if (!split_key_map(tmp, &handler, &arg))
+ return;
+ switch(*handler) {
+ case 'i':
+ handler = "internal";
+ desc = command_list[find_cmd_byname(arg)].description;
+ break;
+ case 'x': handler = "external"; break;
+ case 'd': handler = "display "; break;
+ case 'p': handler = "para "; break;
+ }
+ outputf(COLOR_MSG, "%s\t%s\t%s%s\t%s", tmp, handler, arg,
+ strlen(arg) < 8? "\t" : "",
+ desc);
+ }
+ for (i = 0; command_list[i].handler; i++) {
+ struct gui_command gc = command_list[i];
+
+ outputf(COLOR_MSG, "%s\tinternal\t%s\t%s%s", gc.key, gc.name,
+ strlen(gc.name) < 8? "\t" : "",
+ gc.description);
+ }
+ print_in_bar(COLOR_MSG, "try \"para_gui -h\" or \"para_client help\" "
+ "for more info");
+ return;
+}
+
+static void com_shrink_top_win(void)
+{
+ if (top.lines <= theme.top_lines_min) {
+ PARA_WARNING_LOG("%s", "can not decrease top window");
+ return;
+ }
+ init_wins(top.lines - 1);
+ wclear(top.win);
+ print_all_items();
+ print_in_bar(COLOR_MSG, "%s", "decreased top window");
+}
+
+static void com_enlarge_top_win(void)
+{
+ if (bot.lines < 3) {
+ PARA_WARNING_LOG("%s", "can not increase top window");
+ return;
+ }
+ init_wins(top.lines + 1);
+ wclear(top.win);
+ print_all_items();
+ print_in_bar(COLOR_MSG, "increased top window");
+}
+
+static void com_version(void)
+{
+ print_in_bar(COLOR_MSG, "para_gui " VERSION " \"" CODENAME "\"");
+}
+
+static void com_quit(void)
+{
+ finish(0);
+}
+
+static void com_refresh(void)
+{
+ shutdown_curses();
+ init_curses();
+}
+
+static void change_theme(int next)
+{
+ if (next)
+ next_theme(&theme);
+ else
+ prev_theme(&theme);
+ /* This seems to be needed twice, why? */
+ com_refresh();
+ com_refresh();
+ PARA_NOTICE_LOG("new theme: %s", theme.name);
+}
+
+static void com_next_theme(void)
+{
+ change_theme(1);
+}
+
+static void com_prev_theme(void)
+{
+ change_theme(0);
+}
+
+
+static void handle_command(int c)
+{
+ int i;
+
+ /* first check user's key bindings */
+ for (i = 0; i < conf.key_map_given; ++i) {
+ char tmp[MAXLINE], *handler, *arg;
+
+ strcpy(tmp, conf.key_map_arg[i]);
+ if (!split_key_map(tmp, &handler, &arg))
+ return;
+ if (!strcmp(tmp, km_keyname(c))) {
+ if (*handler == 'd') {
+ display_cmd(arg);
+ return;
+ }
+ if (*handler == 'x') {
+ external_cmd(arg);
+ return;
+ }
+ if (*handler == 'p') {
+ client_cmd_cmdline(arg);
+ return;
+ }
+ if (*handler == 'i') {
+ int num = find_cmd_byname(arg);
+ if (num >= 0)
+ command_list[num].handler();
+ return;
+ }
+ }
+ }
+ /* not found, check internal key bindings */
+ for (i = 0; command_list[i].handler; i++) {
+ if (!strcmp(km_keyname(c), command_list[i].key)) {
+ command_list[i].handler();
+ return;
+ }
+ }
+ print_in_bar(COLOR_ERRMSG, "key '%s' is not bound, press ? for help",
+ km_keyname(c));
+}
+
+int main(int argc, char *argv[])
+{
+ int ret;
+ char *cf;
+
+ _argc = argc;
+ _argv = argv;
+
+ if (cmdline_parser(argc, argv, &conf)) {
+ fprintf(stderr, "parse error while reading command line\n");
+ exit(EXIT_FAILURE);
+ }
+ init_theme(0, &theme);
+ top.lines = theme.top_lines_default;
+ if (check_key_map_args() < 0) {
+ fprintf(stderr, "invalid key map\n");
+ exit(EXIT_FAILURE);
+ }
+ cf = configfile_exists();
+ if (!cf && conf.config_file_given) {
+ fprintf(stderr, "can not read config file %s\n",
+ conf.config_file_arg);
+ exit(EXIT_FAILURE);
+ }
+ if (cf)
+ cmdline_parser_configfile(cf, &conf, 0, 0, 0);
+ if (check_key_map_args() < 0) {
+ fprintf(stderr, "invalid key map in config file\n");
+ exit(EXIT_FAILURE);
+ }
+ setup_signal_handling();
+ bot_win_rb = ringbuffer_new(RINGBUFFER_SIZE);
+ initscr(); /* needed only once, always successful */
+ init_curses();
+ print_welcome();
+ for (;;) {
+ print_status_bar();
+ ret = do_select(GETCH_MODE);
+ if (!ret)
+ continue;
+ print_in_bar(COLOR_MSG, " ");
+ handle_command(ret);
+ }
+}
--- /dev/null
+# modify to suit your needs
+
+# jump around in current song with F1-F10
+key_map "^:p:jmp 0"
+key_map "<F1>:p:jmp 10"
+key_map "<F2>:p:jmp 20"
+key_map "<F3>:p:jmp 30"
+key_map "<F4>:p:jmp 40"
+key_map "<F5>:p:jmp 50"
+key_map "<F6>:p:jmp 60"
+key_map "<F7>:p:jmp 70"
+key_map "<F8>:p:jmp 80"
+key_map "<F9>:p:jmp 90"
+key_map "<F10>:p:jmp 97"
+
+#key mappings for standard options
+key_map "s:p:play"
+key_map "$:p:next"
+key_map "p:p:pause"
+key_map " :p:stop"
+
+# jump forward and backward in current song with j,k,J,K
+key_map "k:p:ff 10"
+key_map "j:p:ff 10-"
+key_map "K:p:ff 60"
+key_map "J:p:ff 60-"
+
+# misc stuff
+key_map "l:p:last"
+key_map "u:p:uptime"
+key_map "A:p:laa"
+key_map "S:p:streams"
+
+# shell escapes
+key_map "!:x:/bin/bash"
+key_map "::x:para_client"
+
+# display commands
+# key_map "Q:d:aumix -q"
+
+# do something like this to control volume
+#key_map "5:d:aumix -v 50"
--- /dev/null
+section "general options"
+option "auto_decode" a "auto-decode audio stream" flag on
+option "config_file" c "(default='~/.paraslash/gui.conf')" string typestr="filename" no
+option "loglevel" l "set loglevel (0-6)" int typestr="level" default="4" no
+option "timeout" t "set timeout" int typestr="milliseconds" default="300" no
+option "stat_cmd" s "command to read server and audiod status data from" string typestr="command" default="para_audioc -t 100 stat" no
+
+section "mapping keys to commands"
+option "key_map" k "Map key k to command c using mode m. Mode may be d, x or p for display, external and paraslash commands, respectively. Of course, this option may be given multiple times, one for each key mapping." string typestr="k:m:c" no multiple
--- /dev/null
+#include "para.h"
+
+extern const char *status_item_list[NUM_STAT_ITEMS];
+
+
+int para_open_audiod_pipe(char *cmd)
+{
+ int fds[3] = {0, 1, 0};
+ pid_t pid;
+ return para_exec_cmdline_pid(&pid, cmd, fds) > 0?
+ fds[1] : -1;
+}
+
+int read_audiod_pipe(int fd, void (*line_handler)(char *) )
+{
+ static char buf[STRINGSIZE + 1];
+ static ssize_t loaded, bufsize = sizeof(buf);
+ ssize_t ret;
+
+ if (loaded >= bufsize)
+ loaded = 0;
+ ret = read(fd, buf + loaded, bufsize - loaded);
+ if (ret > 0) {
+ loaded += ret;
+ buf[loaded] = '\0';
+ loaded = for_each_line(buf, loaded, line_handler, 0);
+ }
+ return ret;
+}
--- /dev/null
+/*
+ * Copyright (C) 2005 Andre Noll <maan@systemlinux.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+ */
+
+#include "para.h"
+#include <curses.h>
+
+#define NUM_THEMES 2
+
+
+static int current_theme_num;
+
+static void init_theme_simple(struct gui_theme *t)
+{
+ struct stat_item_data *d = t->data;
+ t->name = "simple";
+ t->author = "Andre Noll";
+ t->lines_min = 5;
+ t->top_lines_min = 2;
+ t->cols_min = 40;
+ t->top_lines_default = 2;
+ t->sb_bg = COLOR_CYAN;
+ t->sb_fg = COLOR_BLACK;
+ t->cmd_bg = COLOR_WHITE;
+ t->cmd_fg = COLOR_BLACK;
+ t->output_bg = COLOR_BLUE;
+ t->output_fg = COLOR_WHITE;
+ t->msg_bg = COLOR_BLUE;
+ t->msg_fg = COLOR_YELLOW;
+ t->err_msg_bg = COLOR_RED;
+ t->err_msg_fg = COLOR_WHITE;
+ t->welcome_bg = COLOR_BLUE;
+ t->welcome_fg = COLOR_WHITE;
+ t->sep_bg = COLOR_BLUE;
+ t->sep_fg = COLOR_CYAN;
+ t->default_fg = COLOR_WHITE;
+ t->default_bg = COLOR_BLUE;
+ t->sep_str = "*";
+
+ d[SI_STATUS_BAR].prefix = "";
+ d[SI_STATUS_BAR].postfix = "";
+ d[SI_STATUS_BAR].fg = COLOR_WHITE;
+ d[SI_STATUS_BAR].bg = COLOR_BLUE;
+ d[SI_STATUS_BAR].align = CENTER;
+ d[SI_STATUS_BAR].x = 0;
+ d[SI_STATUS_BAR].y = 7;
+ d[SI_STATUS_BAR].len = 100;
+
+ d[SI_STATUS].prefix = "para_server: ";
+ d[SI_STATUS].postfix = "";
+ d[SI_STATUS].fg = COLOR_WHITE;
+ d[SI_STATUS].bg = COLOR_BLUE;
+ d[SI_STATUS].align = CENTER;
+ d[SI_STATUS].x = 0;
+ d[SI_STATUS].y = 60;
+ d[SI_STATUS].len = 50;
+
+ d[SI_AUDIOD_STATUS].prefix = "para_audiod: ";
+ d[SI_AUDIOD_STATUS].postfix = "";
+ d[SI_AUDIOD_STATUS].fg = COLOR_WHITE;
+ d[SI_AUDIOD_STATUS].bg = COLOR_BLUE;
+ d[SI_AUDIOD_STATUS].align = CENTER;
+ d[SI_AUDIOD_STATUS].x = 50;
+ d[SI_AUDIOD_STATUS].y = 60;
+ d[SI_AUDIOD_STATUS].len = 50;
+
+}
+
+static void init_theme_colorful_blackness(struct gui_theme *t)
+{
+ struct stat_item_data *d = t->data;
+ t->name = "colorful blackness";
+ t->author = "Andre Noll";
+ /* minimal number of lines that is needed to display all
+ * information provided by this theme
+ */
+ t->lines_min = 15;
+ t->cols_min = 80;
+ t->top_lines_min = 9;
+ t->top_lines_default = 11; /* default number of lines */
+
+ t->sb_bg = COLOR_GREEN; /* status bar background */
+ t->sb_fg = COLOR_BLACK; /* status bar foreground */
+ t->cmd_bg = COLOR_BLACK;
+ t->cmd_fg = COLOR_YELLOW;
+ t->output_bg = COLOR_BLACK;
+ t->output_fg = COLOR_CYAN;
+ t->msg_bg = COLOR_BLACK;
+ t->msg_fg = COLOR_WHITE;
+ t->err_msg_bg = COLOR_RED;
+ t->err_msg_fg = COLOR_WHITE;
+ t->welcome_bg = COLOR_BLUE;
+ t->welcome_fg = COLOR_WHITE;
+ t->sep_bg = COLOR_BLACK; /* color of the separator */
+ t->sep_fg = COLOR_BLUE;
+ t->sep_str = "-";
+ t->default_bg = COLOR_BLACK;
+ t->default_fg = COLOR_MAGENTA;
+
+
+ d[SI_PLAY_TIME].prefix = "";
+ d[SI_PLAY_TIME].postfix = "";
+ d[SI_PLAY_TIME].fg = COLOR_CYAN;
+ d[SI_PLAY_TIME].bg = COLOR_BLACK;
+ d[SI_PLAY_TIME].align = CENTER;
+ d[SI_PLAY_TIME].x = 0;
+ d[SI_PLAY_TIME].y = 7;
+ d[SI_PLAY_TIME].len = 35;
+
+ d[SI_STATUS_BAR].prefix = "";
+ d[SI_STATUS_BAR].postfix = "";
+ d[SI_STATUS_BAR].fg = COLOR_CYAN;
+ d[SI_STATUS_BAR].bg = COLOR_BLACK;
+ d[SI_STATUS_BAR].align = LEFT;
+ d[SI_STATUS_BAR].x = 35;
+ d[SI_STATUS_BAR].y = 7;
+ d[SI_STATUS_BAR].len = 65;
+
+ d[SI_STATUS].prefix = "";
+ d[SI_STATUS].postfix = " ";
+ d[SI_STATUS].fg = COLOR_RED;
+ d[SI_STATUS].bg = COLOR_BLACK;
+ d[SI_STATUS].align = RIGHT;
+ d[SI_STATUS].x = 0;
+ d[SI_STATUS].y = 17;
+ d[SI_STATUS].len = 11;
+
+ d[SI_STATUS_FLAGS].prefix = "(";
+ d[SI_STATUS_FLAGS].postfix = ")";
+ d[SI_STATUS_FLAGS].fg = COLOR_RED;
+ d[SI_STATUS_FLAGS].bg = COLOR_BLACK;
+ d[SI_STATUS_FLAGS].align = LEFT;
+ d[SI_STATUS_FLAGS].x = 11;
+ d[SI_STATUS_FLAGS].y = 17;
+ d[SI_STATUS_FLAGS].len = 10;
+
+ d[SI_DBTOOL].prefix = "dbtool: ";
+ d[SI_DBTOOL].postfix = "";
+ d[SI_DBTOOL].fg = COLOR_RED;
+ d[SI_DBTOOL].bg = COLOR_BLACK;
+ d[SI_DBTOOL].align = CENTER;
+ d[SI_DBTOOL].x = 21;
+ d[SI_DBTOOL].y = 17;
+ d[SI_DBTOOL].len = 20;
+
+ d[SI_FORMAT].prefix = "format: ";
+ d[SI_FORMAT].postfix = "";
+ d[SI_FORMAT].fg = COLOR_RED;
+ d[SI_FORMAT].bg = COLOR_BLACK;
+ d[SI_FORMAT].align = CENTER;
+ d[SI_FORMAT].x = 42;
+ d[SI_FORMAT].y = 17;
+ d[SI_FORMAT].len = 14;
+
+
+ d[SI_NUM_PLAYED].prefix = "#";
+ d[SI_NUM_PLAYED].postfix = "";
+ d[SI_NUM_PLAYED].fg = COLOR_RED;
+ d[SI_NUM_PLAYED].bg = COLOR_BLACK;
+ d[SI_NUM_PLAYED].align = CENTER;
+ d[SI_NUM_PLAYED].x = 60;
+ d[SI_NUM_PLAYED].y = 17;
+ d[SI_NUM_PLAYED].len = 15;
+
+ d[SI_UPTIME].prefix = "SUp: ";
+ d[SI_UPTIME].postfix = "";
+ d[SI_UPTIME].fg = COLOR_RED;
+ d[SI_UPTIME].bg = COLOR_BLACK;
+ d[SI_UPTIME].align = CENTER;
+ d[SI_UPTIME].x = 75;
+ d[SI_UPTIME].y = 17;
+ d[SI_UPTIME].len = 25;
+
+ d[SI_AUDIOD_STATUS].prefix = "audiod: ";
+ d[SI_AUDIOD_STATUS].postfix = "";
+ d[SI_AUDIOD_STATUS].fg = COLOR_MAGENTA;
+ d[SI_AUDIOD_STATUS].bg = COLOR_BLACK;
+ d[SI_AUDIOD_STATUS].align = RIGHT;
+ d[SI_AUDIOD_STATUS].x = 0;
+ d[SI_AUDIOD_STATUS].y = 27;
+ d[SI_AUDIOD_STATUS].len = 13;
+
+
+ d[SI_DECODER_FLAGS].prefix = "[";
+ d[SI_DECODER_FLAGS].postfix = "]";
+ d[SI_DECODER_FLAGS].fg = COLOR_MAGENTA;
+ d[SI_DECODER_FLAGS].bg = COLOR_BLACK;
+ d[SI_DECODER_FLAGS].align = LEFT;
+ d[SI_DECODER_FLAGS].x = 14;
+ d[SI_DECODER_FLAGS].y = 27;
+ d[SI_DECODER_FLAGS].len = 8;
+
+ d[SI_MTIME].prefix = "Mtime: ";
+ d[SI_MTIME].postfix = "";
+ d[SI_MTIME].fg = COLOR_MAGENTA;
+ d[SI_MTIME].bg = COLOR_BLACK;
+ d[SI_MTIME].align = CENTER;
+ d[SI_MTIME].x = 22;
+ d[SI_MTIME].y = 27;
+ d[SI_MTIME].len = 30;
+
+ d[SI_FILE_SIZE].prefix = "Size: ";
+ d[SI_FILE_SIZE].postfix = " kb";
+ d[SI_FILE_SIZE].fg = COLOR_MAGENTA;
+ d[SI_FILE_SIZE].bg = COLOR_BLACK;
+ d[SI_FILE_SIZE].align = CENTER;
+ d[SI_FILE_SIZE].x = 52;
+ d[SI_FILE_SIZE].y = 27;
+ d[SI_FILE_SIZE].len = 26;
+
+ d[SI_AUDIOD_UPTIME].prefix = "AUp: ";
+ d[SI_AUDIOD_UPTIME].postfix = "";
+ d[SI_AUDIOD_UPTIME].fg = COLOR_MAGENTA;
+ d[SI_AUDIOD_UPTIME].bg = COLOR_BLACK;
+ d[SI_AUDIOD_UPTIME].align = CENTER;
+ d[SI_AUDIOD_UPTIME].x = 78;
+ d[SI_AUDIOD_UPTIME].y = 27;
+ d[SI_AUDIOD_UPTIME].len = 22;
+
+
+ d[SI_AUDIO_INFO1].prefix = "";
+ d[SI_AUDIO_INFO1].postfix = "";
+ d[SI_AUDIO_INFO1].fg = COLOR_GREEN;
+ d[SI_AUDIO_INFO1].bg = COLOR_BLACK;
+ d[SI_AUDIO_INFO1].align = CENTER;
+ d[SI_AUDIO_INFO1].x = 0;
+ d[SI_AUDIO_INFO1].y = 43;
+ d[SI_AUDIO_INFO1].len = 100;
+
+ d[SI_AUDIO_INFO2].prefix = "";
+ d[SI_AUDIO_INFO2].postfix = "";
+ d[SI_AUDIO_INFO2].fg = COLOR_GREEN;
+ d[SI_AUDIO_INFO2].bg = COLOR_BLACK;
+ d[SI_AUDIO_INFO2].align = CENTER;
+ d[SI_AUDIO_INFO2].x = 0;
+ d[SI_AUDIO_INFO2].y = 53;
+ d[SI_AUDIO_INFO2].len = 100;
+
+ d[SI_AUDIO_INFO3].prefix = "";
+ d[SI_AUDIO_INFO3].postfix = "";
+ d[SI_AUDIO_INFO3].fg = COLOR_GREEN;
+ d[SI_AUDIO_INFO3].bg = COLOR_BLACK;
+ d[SI_AUDIO_INFO3].align = CENTER;
+ d[SI_AUDIO_INFO3].x = 0;
+ d[SI_AUDIO_INFO3].y = 63;
+ d[SI_AUDIO_INFO3].len = 100;
+
+ d[SI_DBINFO1].prefix = "";
+ d[SI_DBINFO1].postfix = "";
+ d[SI_DBINFO1].fg = COLOR_YELLOW;
+ d[SI_DBINFO1].bg = COLOR_BLACK;
+ d[SI_DBINFO1].align = CENTER;
+ d[SI_DBINFO1].x = 0;
+ d[SI_DBINFO1].y = 77;
+ d[SI_DBINFO1].len = 100;
+
+ d[SI_DBINFO2].prefix = "";
+ d[SI_DBINFO2].postfix = "";
+ d[SI_DBINFO2].fg = COLOR_YELLOW;
+ d[SI_DBINFO2].bg = COLOR_BLACK;
+ d[SI_DBINFO2].align = CENTER;
+ d[SI_DBINFO2].x = 0;
+ d[SI_DBINFO2].y = 87;
+ d[SI_DBINFO2].len = 100;
+
+ d[SI_DBINFO3].prefix = "";
+ d[SI_DBINFO3].postfix = "";
+ d[SI_DBINFO3].fg = COLOR_YELLOW;
+ d[SI_DBINFO3].bg = COLOR_BLACK;
+ d[SI_DBINFO3].align = CENTER;
+ d[SI_DBINFO3].x = 0;
+ d[SI_DBINFO3].y = 97;
+ d[SI_DBINFO3].len = 100;
+}
+
+void init_theme(int num, struct gui_theme *t)
+{
+ int i;
+
+ for (i = 0; i < NUM_STAT_ITEMS; i++)
+ t->data[i].len = 0;
+ current_theme_num = num;
+
+ return (num % NUM_THEMES)?
+ init_theme_simple(t) : init_theme_colorful_blackness(t);
+}
+
+void prev_theme(struct gui_theme *t)
+{
+ return init_theme(++current_theme_num, t);
+}
+
+void next_theme(struct gui_theme *t)
+{
+ return init_theme(--current_theme_num, t);
+}
+
--- /dev/null
+/** \file http.h macros used by http_send and http_recv */
+#define HTTP_OK_MSG "HTTP/1.0 200 OK\r\n\r\n"
+#define HTTP_GET_MSG "GET / HTTP/1.0"
--- /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 http_recv.c paraslash's http receiver */
+
+#include "para.h"
+
+#include "http.h"
+#include "recv.h"
+#include "http_recv.cmdline.h"
+#include "error.h"
+#include "net.h"
+#include "string.h"
+
+/** the output buffer size of the http receiver */
+#define BUFSIZE (32 * 1024)
+
+/**
+ * the possible states of a http receiver node
+ *
+ * \sa receiver_node
+ */
+enum http_recv_status {HTTP_CONNECTED, HTTP_SENT_GET_REQUEST, HTTP_STREAMING};
+
+/**
+ * data specific to the http receiver
+ *
+ * Each running instance of the http receiver reserves space for one such struct.
+ */
+struct private_http_recv_data {
+/**
+ *
+ *
+ * the current status of the http receiver node
+ *
+ * It gets initialized to #HTTP_CONNECTED by the open function of the
+ * http receiver.
+ *
+ * \sa receiver::open, receiver_node.
+ */
+ enum http_recv_status status;
+/**
+ *
+ *
+ * the file descriptor used for receiving the http stream
+ *
+ * The pre_select function of the http receiver adds this file descriptor to
+ * the set of file decriptors which are checked for reading/writing (depending
+ * on the current status) by the select loop of the application (para_audiod or
+ * para_recv).
+ *
+ * The post_select function of the http receiver uses \a fd, if ready, to
+ * establish the http connection, and updates \a status according to the new
+ * state of the connection. As soon as \a status is #HTTP_STREAMING, \a fd is
+ * going to be only checked for reading. If data is available, it is read into
+ * the output buffer of the receiver node by post_select.
+ *
+ * \sa receiver::pre_select receiver::post_select receiver_node
+ */
+ int fd;
+};
+
+static void http_shutdown(void)
+{
+ return;
+}
+
+static char *make_request_msg(void)
+{
+ char *ret, *hn = para_hostname();
+ ret = make_message("%s\nHost: %s\nUser-Agent: para_recv/%s\n\n\n",
+ HTTP_GET_MSG, hn, VERSION);
+ free(hn);
+ return ret;
+}
+
+static int http_pre_select(struct receiver_node *rn, fd_set *rfds, fd_set *wfds,
+ __unused struct timeval *timeout)
+{
+ struct private_http_recv_data *phd = rn->private_data;
+
+ if (phd->status == HTTP_CONNECTED)
+ FD_SET(phd->fd, wfds);
+ else
+ FD_SET(phd->fd, rfds);
+ return phd->fd;
+}
+
+static int http_post_select(struct receiver_node *rn, int select_ret,
+ fd_set *rfds, fd_set *wfds)
+{
+ int ret;
+ struct private_http_recv_data *phd = rn->private_data;
+
+ if (!select_ret) /* we're not interested in timeouts */
+ return 1;
+ if (phd->status == HTTP_CONNECTED) {
+ char *rq;
+ if (!FD_ISSET(phd->fd, wfds))
+ return 1; /* nothing to do */
+ rq = make_request_msg();
+ PARA_NOTICE_LOG("%s", "sending http request\n");
+ ret = send_va_buffer(phd->fd, "%s", rq);
+ free(rq);
+ if (ret < 0)
+ return E_SEND_HTTP_REQUEST;
+ phd->status = HTTP_SENT_GET_REQUEST;
+ return 1;
+ }
+ if (!FD_ISSET(phd->fd, rfds))
+ return 1; /* nothing to do */
+ if (phd->status == HTTP_SENT_GET_REQUEST) {
+ ret = recv_pattern(phd->fd, HTTP_OK_MSG, MAXLINE);
+ if (ret < 0)
+ return -E_MISSING_OK;
+ PARA_NOTICE_LOG("%s", "received ok msg, streaming\n");
+ phd->status = HTTP_STREAMING;
+ return 1;
+ }
+ /* already streaming */
+ if (rn->loaded >= BUFSIZE) {
+ PARA_ERROR_LOG("%s", "buffer overrun\n");
+ return -E_OVERRUN;
+ }
+ ret = recv_bin_buffer(phd->fd, rn->buf + rn->loaded, BUFSIZE - rn->loaded);
+ if (ret <= 0) {
+ PARA_NOTICE_LOG("recv returned %d/%d\n", ret, BUFSIZE - rn->loaded);
+ return ret < 0? -E_HTTP_RECV_BUF : 0;
+ }
+ rn->loaded += ret;
+ return 1;
+}
+
+static void http_recv_close(struct receiver_node *rn)
+{
+ struct private_http_recv_data *phd = rn->private_data;
+ close(phd->fd);
+ free(rn->buf);
+ free(rn->private_data);
+}
+
+static void *http_recv_parse_config(int argc, char **argv)
+{
+ struct gengetopt_args_info *tmp = para_calloc(sizeof(struct gengetopt_args_info));
+
+ if (!http_recv_cmdline_parser(argc, argv, tmp))
+ return tmp;
+ free(tmp);
+ return NULL;
+}
+
+static int http_recv_open(struct receiver_node *rn)
+{
+ struct private_http_recv_data *phd;
+ struct hostent *he;
+ struct gengetopt_args_info *conf = rn->conf;
+ struct sockaddr_in their_addr;
+ int ret;
+
+ rn->buf = para_calloc(BUFSIZE);
+ rn->private_data = para_calloc(sizeof(struct private_http_recv_data));
+ phd = rn->private_data;
+ optind = 0;
+ ret = -E_HOST_INFO;
+ if (!(he = get_host_info(conf->host_arg)))
+ goto err_out;
+ /* get new socket */
+ ret = -E_SOCKET;
+ if ((phd->fd = get_socket()) < 0)
+ goto err_out;
+ /* init their_addr */
+ init_sockaddr(&their_addr, conf->port_arg, he);
+ /* connect */
+ PARA_NOTICE_LOG("connecting to %s:%d\n", conf->host_arg,
+ conf->port_arg);
+ ret = para_connect(phd->fd, &their_addr);
+ if (ret < 0)
+ goto err_out;
+ phd->status = HTTP_CONNECTED;
+ return 1;
+err_out:
+ free(rn->private_data);
+ free(rn->buf);
+ return ret;
+}
+
+/**
+ * the init function of the http receiver
+ *
+ * \param r pointer to the receiver struct to initialize
+ *
+ * Just initialize all function pointers of \a r.
+ */
+void http_recv_init(struct receiver *r)
+{
+ r->open = http_recv_open;
+ r->close = http_recv_close;
+ r->pre_select = http_pre_select;
+ r->post_select = http_post_select;
+ r->shutdown = http_shutdown;
+ r->parse_config = http_recv_parse_config;
+}
--- /dev/null
+option "host" i "ip or host" string default="localhost" no
+option "port" p "tcp port to connect to" int default="8000" no
--- /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 http_send.c paraslash's http sender */
+
+
+#include "server.cmdline.h"
+#include "server.h"
+#include "http.h"
+#include "afs.h"
+#include "send.h"
+#include "list.h"
+#include "close_on_fork.h"
+#include "error.h"
+#include "net.h"
+#include "string.h"
+
+/** \cond convert sock_addr_in to ascii */
+#define CLIENT_ADDR(hc) inet_ntoa((hc)->addr.sin_addr)
+/* get the port number of a struct http_client */
+#define CLIENT_PORT(hc) (hc)->addr.sin_port
+#define HTTP_ERR_MSG "HTTP/1.0 400 Bad Request\n"
+/** \endcond */
+
+extern struct gengetopt_args_info conf;
+
+/** the possible states of a client from the server's POV */
+enum http_status {
+ HTTP_CONNECTED,
+ HTTP_GOT_GET_REQUEST,
+ HTTP_SENT_OK_MSG,
+ HTTP_READY_TO_STREAM,
+ HTTP_STREAMING,
+ HTTP_INVALID_GET_REQUEST
+};
+
+/** clients will be kicked if there are more than that many bytes pending */
+#define MAX_BACKLOG 40000
+/** the list of connected clients **/
+static struct list_head clients;
+/** the whitelist/blacklist */
+static struct list_head access_perm_list;
+
+/** describes one client that connected the tcp port of the http sender */
+struct http_client {
+/** the file descriptor of the client */
+ int fd;
+/** address information about the client */
+ struct sockaddr_in addr;
+/** the client's current status */
+ enum http_status status;
+/** non-zero if we included \a fd in the read set */
+ int check_r;
+/** non-zero if we included \a fd in the write set */
+ int check_w;
+/** the position of this client in the client list */
+ struct list_head node;
+/** the list of pending packets for this client */
+ struct list_head packet_queue;
+/** the number of pending bytes for this client */
+ unsigned long pq_bytes;
+};
+
+/**
+ * describes one queued data packet for a client
+ *
+ * The send function of the http sender checks each client fd for writing. If a
+ * client fd is not ready, it tries to queue that packet for this client until
+ * the number of queued bytes exceeds \p MAX_BACKLOG.
+ */
+struct queued_packet {
+/** the length of the packet in bytes */
+ unsigned int len;
+/** pointer to the packet data */
+ char *packet;
+/** position of the packet in the packet list */
+ struct list_head node;
+};
+
+/**
+ * describes one entry in the blacklist/whitelist of the http sender
+ */
+struct access_info {
+ /** the address to be black/whitelisted */
+ struct in_addr addr;
+ /** the netmask for this entry */
+ int netmask;
+ /** the position of this entry in the access_perm_list */
+ struct list_head node;
+};
+
+static int server_fd = -1, numclients;
+static struct sender *self;
+
+
+static void http_shutdown_client(struct http_client *hc, const char *msg)
+{
+ struct queued_packet *qp, *tmp;
+ PARA_INFO_LOG("shutting down %s on fd %d (%s)\n", CLIENT_ADDR(hc),
+ hc->fd, msg);
+ numclients--;
+ close(hc->fd);
+
+ list_for_each_entry_safe(qp, tmp, &hc->packet_queue, node) {
+ free(qp->packet);
+ list_del(&qp->node);
+ free(qp);
+ }
+ list_del(&hc->node);
+ free(hc);
+ return;
+}
+
+static void http_shutdown_clients_real(void)
+{
+ struct http_client *hc, *tmp;
+ list_for_each_entry_safe(hc, tmp, &clients, node)
+ http_shutdown_client(hc, "afs request");
+}
+static void http_shutdown_clients(void)
+{
+ struct http_client *hc, *tmp;
+ list_for_each_entry_safe(hc, tmp, &clients, node)
+ if (hc->status == HTTP_STREAMING)
+ http_shutdown_client(hc, "afs request");
+}
+
+static int http_send_msg(struct http_client *hc, const char *msg)
+{
+ int ret = send_buffer(hc->fd, msg);
+
+ if (ret < 0)
+ http_shutdown_client(hc, "send msg failed");
+ return ret;
+}
+
+static void http_send_ok_msg(struct http_client *hc)
+{
+ PARA_INFO_LOG("sending http ok message to fd %d\n", hc->fd);
+ http_send_msg(hc, HTTP_OK_MSG);
+}
+
+static int http_send_err_msg(struct http_client *hc)
+{
+ PARA_NOTICE_LOG("sending bad request message to fd %d\n", hc->fd);
+ return http_send_msg(hc, HTTP_ERR_MSG);
+}
+
+static int queue_packet(struct http_client *hc, const char *buf, size_t len)
+{
+ struct queued_packet *qp;
+ if (hc->pq_bytes + len > MAX_BACKLOG) {
+ http_shutdown_client(hc, "packet queue overrun");
+ return -E_QUEUE;
+ }
+ qp = para_malloc(sizeof(struct queued_packet));
+ hc->pq_bytes += len;
+ qp->packet = para_malloc(len);
+ memcpy(qp->packet, buf, len);
+ qp->len = len;
+ list_add_tail(&qp->node, &hc->packet_queue);
+ PARA_INFO_LOG("%lu bytes queued for fd %d\n", hc->pq_bytes, hc->fd);
+ return 1;
+}
+
+static int write_ok(int fd)
+{
+ struct timeval tv = {0, 0};
+ fd_set wfds;
+ int ret;
+again:
+ FD_ZERO(&wfds);
+ FD_SET(fd, &wfds);
+ ret = select(fd + 1, NULL, &wfds, NULL, &tv);
+ if (ret < 0 && errno == EINTR)
+ goto again;
+ if (ret < 0)
+ ret = -E_WRITE_OK;
+ return ret;
+}
+
+
+static int send_queued_packets(struct http_client *hc)
+{
+ int ret;
+ struct queued_packet *qp, *tmp;
+
+ if (list_empty(&hc->packet_queue))
+ return 1;
+ list_for_each_entry_safe(qp, tmp, &hc->packet_queue, node) {
+ ret = write_ok(hc->fd);
+ if (ret <= 0)
+ return ret;
+ ret = write(hc->fd, qp->packet, qp->len);
+ if (ret < 0)
+ return ret;
+ if (ret != qp->len) {
+ qp->len -= ret;
+ memmove(qp->packet, qp->packet + ret, qp->len);
+ return 0;
+ }
+ hc->pq_bytes -= qp->len;
+ free(qp->packet);
+ list_del(&qp->node);
+ free(qp);
+ }
+ return 1;
+}
+
+static void http_send(__unused struct audio_format *af,
+ long unsigned current_chunk,
+ __unused long unsigned chunks_sent, const char *buf, size_t len)
+{
+ struct http_client *hc, *tmp;
+ int ret;
+
+ list_for_each_entry_safe(hc, tmp, &clients, node) {
+ if (hc->status != HTTP_STREAMING &&
+ hc->status != HTTP_READY_TO_STREAM)
+ continue;
+ if (hc->status == HTTP_READY_TO_STREAM) {
+ if (af->get_header_info && current_chunk) {
+ /* need to send header */
+ int hlen;
+ char *buf = af->get_header_info(&hlen);
+ if (!buf || hlen <= 0)
+ continue; /* header not yet available */
+ PARA_INFO_LOG("queueing header: %d\n", hlen);
+ if (queue_packet(hc, buf, hlen) < 0)
+ continue;
+ } else
+ PARA_INFO_LOG("%s", "no need to queue header\n");
+ hc->status = HTTP_STREAMING;
+ }
+ ret = send_queued_packets(hc);
+ if (ret < 0) {
+ http_shutdown_client(hc, "send error");
+ continue;
+ }
+ if (!len)
+ continue;
+ if (!ret || write_ok(hc->fd) <= 0) {
+ PARA_INFO_LOG("fd %d not ready (%lu bytes queued),"
+ " trying to queue packet\n", hc->fd,
+ hc->pq_bytes);
+ queue_packet(hc, buf, len);
+ continue;
+ }
+// PARA_DEBUG_LOG("sending %d -> %s\n", len, CLIENT_ADDR(hc));
+ ret = write(hc->fd, buf, len);
+ if (ret < 0) {
+ http_shutdown_client(hc, "send error");
+ continue;
+ }
+ if (ret != len)
+ queue_packet(hc, buf + ret, len - ret);
+ }
+}
+
+static int host_in_access_perm_list(struct http_client *hc)
+{
+ struct access_info *ai, *tmp;
+ list_for_each_entry_safe(ai, tmp, &access_perm_list, node) {
+ unsigned mask = ((~0) >> ai->netmask);
+ if ((hc->addr.sin_addr.s_addr & mask) == (ai->addr.s_addr & mask))
+ return 1;
+ }
+ return 0;
+}
+
+static void http_post_select(__unused struct audio_format *af, fd_set *rfds,
+ fd_set *wfds)
+{
+ int i = -1, match;
+ struct http_client *hc, *tmp;
+ char *err_msg;
+
+ list_for_each_entry_safe(hc, tmp, &clients, node) {
+ i++;
+// PARA_DEBUG_LOG("handling client %d: %s\n", i, CLIENT_ADDR(hc));
+ switch (hc->status) {
+ case HTTP_STREAMING: /* nothing to do */
+ case HTTP_READY_TO_STREAM:
+ break;
+ case HTTP_CONNECTED: /* need to recv get request */
+ if (hc->check_r && FD_ISSET(hc->fd, rfds)) {
+ if (recv_pattern(hc->fd, HTTP_GET_MSG, MAXLINE)
+ < 0) {
+ hc->status = HTTP_INVALID_GET_REQUEST;
+ } else {
+ hc->status = HTTP_GOT_GET_REQUEST;
+ PARA_INFO_LOG("%s",
+ "received get request\n");
+ }
+ }
+ break;
+ case HTTP_GOT_GET_REQUEST: /* need to send ok msg */
+ if (hc->check_w && FD_ISSET(hc->fd, wfds)) {
+ hc->status = HTTP_SENT_OK_MSG;
+ http_send_ok_msg(hc);
+ }
+ break;
+ case HTTP_INVALID_GET_REQUEST: /* need to send err msg */
+ if (hc->check_w && FD_ISSET(hc->fd, wfds)) {
+ if (http_send_err_msg(hc) >= 0)
+ http_shutdown_client(hc,
+ "invalid get request");
+ }
+ break;
+ case HTTP_SENT_OK_MSG: /* need to send header? */
+ if (hc->check_w && FD_ISSET(hc->fd, wfds))
+ hc->status = HTTP_READY_TO_STREAM;
+ break;
+ }
+ }
+ if (!FD_ISSET(server_fd, rfds))
+ return;
+ hc = para_calloc(sizeof(struct http_client));
+ err_msg = "accept error";
+ hc->fd = para_accept(server_fd, &hc->addr, sizeof(struct sockaddr_in));
+ if (hc->fd <= 0)
+ goto err_out;
+ PARA_NOTICE_LOG("connection from %s (fd %d)\n", CLIENT_ADDR(hc), hc->fd);
+ if (conf.http_max_clients_arg > 0 && numclients >=
+ conf.http_max_clients_arg) {
+ err_msg = "server full";
+ goto err_out;
+ }
+ match = host_in_access_perm_list(hc);
+ PARA_DEBUG_LOG("host_in_access_perm_list: %d\n", match);
+ if ((match && !conf.http_default_deny_given) ||
+ (!match && conf.http_default_deny_given)) {
+ err_msg = "permission denied";
+ goto err_out;
+ }
+ hc->status = HTTP_CONNECTED;
+ INIT_LIST_HEAD(&hc->packet_queue);
+ PARA_INFO_LOG("accepted client #%d: %s (fd %d)\n", numclients,
+ CLIENT_ADDR(hc), hc->fd);
+ numclients++;
+ list_add(&hc->node, &clients);
+ return;
+err_out:
+ PARA_WARNING_LOG("ignoring connect request from %s (%s)\n",
+ CLIENT_ADDR(hc), err_msg);
+ if (hc->fd > 0)
+ close(hc->fd);
+ free(hc);
+}
+
+static void http_pre_select(struct audio_format *af, int *max_fileno, fd_set *rfds,
+ fd_set *wfds)
+{
+ struct http_client *hc, *tmp;
+
+ if (server_fd < 0)
+ return;
+ FD_SET(server_fd, rfds);
+ *max_fileno = MAX(*max_fileno, server_fd);
+ list_for_each_entry_safe(hc, tmp, &clients, node) {
+ //PARA_DEBUG_LOG("hc %p on fd %d: status %d\n", hc, hc->fd, hc->status);
+ hc->check_r = 0;
+ hc->check_w = 0;
+ switch (hc->status) {
+ case HTTP_STREAMING:
+ case HTTP_READY_TO_STREAM:
+ break;
+ case HTTP_CONNECTED: /* need to recv get request */
+ FD_SET(hc->fd, rfds);
+ *max_fileno = MAX(*max_fileno, hc->fd);
+ hc->check_r = 1;
+ break;
+ case HTTP_GOT_GET_REQUEST: /* need to send ok msg */
+ case HTTP_INVALID_GET_REQUEST: /* need to send err msg */
+ FD_SET(hc->fd, wfds);
+ *max_fileno = MAX(*max_fileno, hc->fd);
+ hc->check_w = 1;
+ break;
+ case HTTP_SENT_OK_MSG:
+ if (!af || !afs_playing())
+ break; /* wait until server starts playing */
+ FD_SET(hc->fd, wfds);
+ *max_fileno = MAX(*max_fileno, hc->fd);
+ hc->check_w = 1;
+ break;
+ }
+ }
+}
+
+static int open_tcp_port(int port)
+{
+ server_fd = init_tcp_socket(port);
+ if (server_fd < 0) {
+ http_shutdown_clients_real();
+ self->status = SENDER_OFF;
+ return server_fd;
+ }
+ self->status = SENDER_ON;
+ add_close_on_fork_list(server_fd);
+ return 1;
+}
+
+static int http_com_on(__unused struct sender_command_data *scd)
+{
+ if (self->status == SENDER_ON)
+ return 1;
+ return open_tcp_port(conf.http_port_arg);
+}
+
+static int http_com_off(__unused struct sender_command_data *scd)
+{
+ self->status = SENDER_OFF;
+ if (server_fd > 0) {
+ close(server_fd);
+ del_close_on_fork_list(server_fd);
+ server_fd = -1;
+ }
+ http_shutdown_clients_real();
+ return 1;
+}
+
+static void del_perm_list_entry(struct sender_command_data *scd)
+{
+ struct access_info *ai, *tmp;
+
+ list_for_each_entry_safe(ai, tmp, &access_perm_list, node) {
+ char *nad = para_strdup(inet_ntoa(ai->addr));
+ if (!strcmp(nad, inet_ntoa(scd->addr)) &&
+ ai->netmask == scd->netmask) {
+ PARA_NOTICE_LOG("removing %s/%i from access list\n",
+ nad, ai->netmask);
+ list_del(&ai->node);
+ free(ai);
+ }
+ free(nad);
+ }
+}
+
+static void add_perm_list_entry(struct sender_command_data *scd)
+{
+ struct access_info *ai = para_malloc(sizeof(struct access_info));
+ ai->addr = scd->addr;
+ ai->netmask = scd->netmask;
+ PARA_INFO_LOG("adding %s/%i to access list\n", inet_ntoa(ai->addr),
+ ai->netmask);
+ list_add(&ai->node, &access_perm_list);
+}
+
+static int http_com_deny(struct sender_command_data *scd)
+{
+ if (conf.http_default_deny_given)
+ del_perm_list_entry(scd);
+ else
+ add_perm_list_entry(scd);
+ return 1;
+}
+
+static int http_com_allow(struct sender_command_data *scd)
+{
+ if (conf.http_default_deny_given)
+ add_perm_list_entry(scd);
+ else
+ del_perm_list_entry(scd);
+ return 1;
+}
+
+static char *http_info(void)
+{
+ char *clnts = NULL, *ap = NULL, *ret;
+ struct access_info *ai, *tmp_ai;
+ struct http_client *hc, *tmp_hc;
+
+ list_for_each_entry_safe(ai, tmp_ai, &access_perm_list, node) {
+ char *tmp = make_message("%s%s/%d ", ap? ap : "",
+ inet_ntoa(ai->addr), ai->netmask);
+ free(ap);
+ ap = tmp;
+ }
+ list_for_each_entry_safe(hc, tmp_hc, &clients, node) {
+ char *tmp = make_message("%s%s:%d ", clnts? clnts : "",
+ CLIENT_ADDR(hc), CLIENT_PORT(hc));
+ free(clnts);
+ clnts = tmp;
+ }
+ ret = make_message(
+ "http status: %s\n"
+ "http tcp port: %d\n"
+ "http clients: %d\n"
+ "http maximal number of clients: %d%s\n"
+ "http connected clients: %s\n"
+ "http access %s list: %s\n",
+ (self->status == SENDER_ON)? "on" : "off",
+ conf.http_port_arg,
+ numclients,
+ conf.http_max_clients_arg,
+ conf.http_max_clients_arg > 0? "" : " (unlimited)",
+ clnts? clnts : "(none)",
+ conf.http_default_deny_given? "allow" : "deny",
+ ap? ap : "(none)"
+ );
+ free(ap);
+ free(clnts);
+ return ret;
+}
+
+static void init_access_control_list(void)
+{
+ int i;
+ struct sender_command_data scd;
+
+ INIT_LIST_HEAD(&access_perm_list);
+ for (i = 0; i < conf.http_access_given; i++) {
+ char *arg = para_strdup(conf.http_access_arg[i]);
+ char *p = strchr(arg, '/');
+ if (!p)
+ goto err;
+ *p = '\0';
+ if (!inet_aton(arg, &scd.addr))
+ goto err;
+ scd.netmask = atoi(++p);
+ if (scd.netmask < 0 || scd.netmask > 32)
+ goto err;
+ add_perm_list_entry(&scd);
+ goto success;
+err:
+ PARA_CRIT_LOG("syntax error for http_access option "
+ "#%d, ignoring\n", i);
+success:
+ free(arg);
+ continue;
+ }
+}
+
+static char *http_help(void)
+{
+ return make_message(
+ "usage: {on|off}\n"
+ "usage: {allow|deny} IP mask\n"
+ "example: allow 127.0.0.1 32\n"
+ );
+}
+
+/**
+ * the init function of the http sender
+ *
+ * \param s pointer to the http sender struct
+ *
+ * It initializes all function pointers of \a s, init the client list and the
+ * acess control list as well. If autostart is wanted, open the tcp port.
+ */
+void http_send_init(struct sender *s)
+{
+ INIT_LIST_HEAD(&clients);
+ s->info = http_info;
+ s->send = http_send;
+ s->pre_select = http_pre_select;
+ s->post_select = http_post_select;
+ s->shutdown_clients = http_shutdown_clients;
+ s->help = http_help;
+ s->client_cmds[SENDER_ON] = http_com_on;
+ s->client_cmds[SENDER_OFF] = http_com_off;
+ s->client_cmds[SENDER_DENY] = http_com_deny;
+ s->client_cmds[SENDER_ALLOW] = http_com_allow;
+ s->client_cmds[SENDER_ADD] = NULL;
+ s->client_cmds[SENDER_DELETE] = NULL;
+ self = s;
+ init_access_control_list();
+ if (!conf.http_no_autostart_given)
+ open_tcp_port(conf.http_port_arg);
+ PARA_DEBUG_LOG("%s", "http sender init complete\n");
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=iso-8859-1'>
+ <title>Paraslash</title>
+ <style type="text/css">
+ body {
+ background-color: #000000;
+ color: #cccccc;
+ }
+ :link { color: #990000 }
+ :visited { color: #990000 }
+ td.c2 {font-family: arial, helvetica, sans-serif; font-size: 80%}
+ td.c1 {font-family: lucida, helvetica; font-size: 248%}
+ a:hover {background:#ff0;}
+ a:hover img {background:#fff;}
+ </style>
+ <link rel="shortcut icon" href="paraslash.ico">
+</head>
+<body>
+ <basefont face="lucida, helvetica, arial" size="3">
+ <table border="0" cellpadding="0" cellspacing="0">
+ <tr>
+ <td>
+ <a href="/"><IMG SRC="paraslash.png" alt="paraslash" border="0"></a><BR>
+ </td>
+ <td>
+ <h3>Paraslash: Play, archive, rate and stream
+ large audio sets happily</h3>
+
+ A set of tools for doing just what its name
+ suggests.
+ </td>
+ </tr>
+ <tr>
+ <td valign="TOP">
+ <br><a href="#feature_list">Feature list</a>
+ <br><a href="#screenshots">Screenshots</a>
+ <br><a href="#download">Download</a>
+ <br><a href="#live_demo">Live Demo</a>
+ <br><a href="#changes">Changes</a> <br><a
+ href="#documentation">Documentation</a>
+ <br><a href="#license">License</a> <br><a
+ href="#author">Author</a>
+ </td>
+ <td Valign="TOP">
+ <hr>
+ <h3>Events</h3>
+ <ul>
+ <li>2005-12-27: paraslash-0.2.7 "transparent invariance"</li>
+ <li>2005-10-29: paraslash-0.2.6 "recursive compensation"</li>
+ <li>2005-10-27: <a href="doc/html/index.html">manual pages</a> online</li>
+ <li>2005-10-13: paraslash-0.2.5 "aggressive resolution"</li>
+ <li>2005-09-21: paraslash-0.2.4 "toxic anticipation"</li>
+ <li>2005-09-01: paraslash-0.2.3 "hydrophilic movement"</li>
+ <li>2005-08-19: paraslash-0.2.2 "tangential excitation"</li>
+ <li>2005-08-15: paraslash-0.2.1 "surreal experience"</li>
+ <li>2005-08-06: overview.pdf</li>
+ <li>2005-08-06: paraslash-0.2.0 "distributed diffusion"</li>
+ <li>2005-08-01: paraslash live stream</li>
+ <li>2005-04-18: paraslash-0.1.7 "melting penetration"</li>
+ <li>2005-03-05: paraslash-0.1.6 "asymptotic balance"</li>
+ <li>2004-12-31: paraslash-0.1.5 "opaque eternity"</li>
+ <li>2004-12-19: paraslash-0.1.4 "tunneling transition"</li>
+ <li>2004-12-10: paraslash-0.1.3 "vanishing inertia"</li>
+ <li>2004-11-28: paraslash-0.1.2 "spherical fluctuation"</li>
+ <li>2004-11-05: paraslash-0.1.1 "floating atmosphere"</li>
+ <li>2004-10-22: paraslash-0.1.0 "rotating cortex"</li>
+ </ul>
+ <hr>
+ <h3><a name="feature_list">Feature list</a></h3>
+ <ul>
+ <li>network audio streaming software</li>
+ <li>client/server tcp-networking</li>
+ <li>command line interface</li>
+ <li> <a href="http://www.openssl.org/">openssl</a> user authentication</li>
+ <li>several grafical user interfaces</li>
+ <li> <a href="http://www.mysql.com">mysql</a>-based audio file selector</li>
+ </ul>
+ See <a href="FEATURES.html">FEATURES</a> for a more detailed list.
+ <hr>
+
+ <h3><a name="screenshots">Screenshots</a></h3>
+ Everybody loves screenshots, so
+ <a href="screenshots/">here</a>
+ we go.
+ <hr>
+
+ <h3><a name="download">Download</a></h3>
+
+ <p> Only <a href="versions/">source</a> is available,
+ including the <a href="versions/paraslash-cvs.tar.bz2">
+ nightly cvs snapshot</a>. All regular releases are
+ <a href="PUBLIC_KEY">cryptographically signed</a>.
+ Anonymous (read-only) cvs access is also
+ available. Checkout a copy with </p>
+
+ <p> cvs -d
+ :pserver:anonymous@cvs.systemlinux.org:/var/lib/cvs
+ login </p>
+ <p>(empty passwd)</p>
+ <p> cvs -d :pserver:anonymous@cvs.systemlinux.org:/var/lib/cvs co paraslash </p>
+
+
+ <p> Finally, you can <a href="HTML/index.html">RTFS online</a>.
+ <hr>
+
+ <h3><a name="live_demo">Live Demo</a></h3>
+
+ <p> There is a public paraslash stream at www.paraslash.org,
+ streaming
+
+ <a href="http://www.digitalvibes.de/">the music of Henri Petterson</a>.
+
+
+ You can listen to the stream with any mp3 player that supports
+ http streaming. Both </p>
+
+ <p>mpg123 http://www.paraslash.org:8009/</p>
+
+ and
+
+ <p>xmms http://www.paraslash.org:8009/</p>
+
+ <p> are known to work.</p>
+
+ <p> Moreover, there is an anonymous paraslash account
+ available which you can use to have a look at paraslash
+ without configuring and running para_server on your own box.
+ Just download and run
+
+ <a href="demo-script">this shell script</a>
+
+ on your Unix system. If you prefer to do things manually,
+ simply cut-and-paste the instructions given below verbatim
+ to your shell. No root-privileges are required.</p>
+
+<ul>
+ <li>
+ Check that both aplay and mpg123 are installed on your system
+ </li>
+
+ <li>
+ <a href="versions">Download </a>
+ a recent paraslash package. You
+ you need paraslash-0.2.0 or later
+ for the demo, paraslash-0.1.x will not work.
+ </li>
+
+ <li>
+
+ Install the neccessary paraslash binaries
+ (you can safely ignore any warnings about
+ missing software):
+
+ <ul>
+ <li>tar xjf paraslash-cvs.tar.bz2</li>
+ <li>cd paraslash-cvs</li>
+ <li>bin="para_client para_audioc para_audiod para_gui" # all we need
+ <li>(./configure --prefix="$HOME" && make $bin) > /dev/null</li>
+ <li> mkdir -p $HOME/bin; cp $bin $HOME/bin
+ <li> export PATH=$HOME/bin:$PATH
+
+ </ul>
+ There should be no errors.
+ </li>
+ <li>
+ Get the key for the anonymous account on
+ www.paraslash.org:
+
+ <ul>
+ <li> dir="$HOME/.paraslash"; server=www.paraslash.org</li>
+ <li> mkdir -p $dir</li>
+ <li> wget --directory-prefix=$dir http://$server/key.anonymous</li>
+ </ul>
+ </li>
+ <li>
+ Tell para_client that we want to connect to
+ www.paraslash.org as user anonymous:
+
+ <ul>
+ <li> conf="$dir/client.conf"; socket="$dir/socket"</li>
+ <li> echo user \"anonymous\" > $conf</li>
+ <li> echo hostname \"$server\" >> $conf</li>
+ <li> echo key_file \"$dir/key.anonymous\" >> $conf</li>
+ <li> echo socket \"$socket\" >> $dir/audioc.conf</li>
+ </ul>
+ </li>
+ <li>
+ Start para_audiod
+ <ul> <li>
+
+ para_audiod -d --stream_read_cmd
+ "mp3:mpg123 -s http://$server:8009/"
+ --stream_write_cmd "mp3:aplay -fcd" -L $dir/audiod.log
+ --socket=$socket
+
+ </li> </ul>
+ </li><li>
+ Start para_gui
+ <ul> <li>
+ para_gui
+ </li> </ul>
+ </li>
+ </ul> <hr>
+
+
+ <h3><a name="changes">Changes</a></h3>
+ Read the complete
+ <a href="ChangeLog.html">ChangeLog</a>
+ or the file
+ <a href="NEWS.html">NEWS</a>
+ containing a brief summary of the changes for each version.
+ <hr>
+
+
+ <h3><a name="documentation">Documentation</a></h3>
+ Have a look at this
+ <a href="overview.pdf">overview</a>,
+ a pdf file containing a sketch which illustrates how the pieces of paraslash work
+ together. Read
+ <a href="README.html">README</a>
+ for general information,
+ <a href="INSTALL.html">INSTALL</a>
+ for installation notes, and
+ <a href="README.mysql.html">README.mysql</a>
+ for instructions on how to use the mysql database tool
+ shipped with paraslash. There is also an online version
+ of paraslash's
+ <a href="doc/html/index.html">manual pages</a>.
+ <hr>
+
+ <h3><a name="license">License</a></h3>
+ Distribution of Paraslash is covered by the GNU GPL. See file
+ <a href="COPYING.html">COPYING</a>.
+ <hr>
+
+ <h3><a name="author">Author</a></h3>
+ André Noll,
+ <a href="mailto:maan@systemlinux.org"
+ >maan@systemlinux.org</a>
+ <p>
+ Several people helped by reporting bugs, improving documentation,
+ constructive discussions, or, last but not least, by writing
+ free software on which this project is based on. See
+ <a href="CREDITS.html">CREDITS</a>
+ for an incomplete list of people.
+ <hr>
+
+
+ Last modified:
+ <!--#flastmod virtual="" -->
+ <p>
+ <a href="http://validator.w3.org/check?uri=referer"><img border="0"
+ src="http://www.w3.org/Icons/valid-html401"
+ alt="Valid HTML 4.01!" height="31" width="88"></a>
+ </p>
+
+ </td>
+ </table>
+</body>
+</html>
--- /dev/null
+#!/bin/sh
+#
+# install - install a program, script, or datafile
+#
+# This originates from X11R5 (mit/util/scripts/install.sh), which was
+# later released in X11R6 (xc/config/util/install.sh) with the
+# following copyright and license.
+#
+# Copyright (C) 1994 X Consortium
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC-
+# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+# Except as contained in this notice, the name of the X Consortium shall not
+# be used in advertising or otherwise to promote the sale, use or other deal-
+# ings in this Software without prior written authorization from the X Consor-
+# tium.
+#
+#
+# FSF changes to this file are in the public domain.
+#
+# Calling this script install-sh is preferred over install.sh, to prevent
+# `make' implicit rules from creating a file called install from it
+# when there is no Makefile.
+#
+# This script is compatible with the BSD install script, but was written
+# from scratch. It can only install one file at a time, a restriction
+# shared with many OS's install programs.
+
+
+# set DOITPROG to echo to test this script
+
+# Don't use :- since 4.3BSD and earlier shells don't like it.
+doit="${DOITPROG-}"
+
+
+# put in absolute paths if you don't have them in your path; or use env. vars.
+
+mvprog="${MVPROG-mv}"
+cpprog="${CPPROG-cp}"
+chmodprog="${CHMODPROG-chmod}"
+chownprog="${CHOWNPROG-chown}"
+chgrpprog="${CHGRPPROG-chgrp}"
+stripprog="${STRIPPROG-strip}"
+rmprog="${RMPROG-rm}"
+mkdirprog="${MKDIRPROG-mkdir}"
+
+transformbasename=""
+transform_arg=""
+instcmd="$mvprog"
+chmodcmd="$chmodprog 0755"
+chowncmd=""
+chgrpcmd=""
+stripcmd=""
+rmcmd="$rmprog -f"
+mvcmd="$mvprog"
+src=""
+dst=""
+dir_arg=""
+
+while [ x"$1" != x ]; do
+ case $1 in
+ -c) instcmd=$cpprog
+ shift
+ continue;;
+
+ -d) dir_arg=true
+ shift
+ continue;;
+
+ -m) chmodcmd="$chmodprog $2"
+ shift
+ shift
+ continue;;
+
+ -o) chowncmd="$chownprog $2"
+ shift
+ shift
+ continue;;
+
+ -g) chgrpcmd="$chgrpprog $2"
+ shift
+ shift
+ continue;;
+
+ -s) stripcmd=$stripprog
+ shift
+ continue;;
+
+ -t=*) transformarg=`echo $1 | sed 's/-t=//'`
+ shift
+ continue;;
+
+ -b=*) transformbasename=`echo $1 | sed 's/-b=//'`
+ shift
+ continue;;
+
+ *) if [ x"$src" = x ]
+ then
+ src=$1
+ else
+ # this colon is to work around a 386BSD /bin/sh bug
+ :
+ dst=$1
+ fi
+ shift
+ continue;;
+ esac
+done
+
+if [ x"$src" = x ]
+then
+ echo "$0: no input file specified" >&2
+ exit 1
+else
+ :
+fi
+
+if [ x"$dir_arg" != x ]; then
+ dst=$src
+ src=""
+
+ if [ -d "$dst" ]; then
+ instcmd=:
+ chmodcmd=""
+ else
+ instcmd=$mkdirprog
+ fi
+else
+
+# Waiting for this to be detected by the "$instcmd $src $dsttmp" command
+# might cause directories to be created, which would be especially bad
+# if $src (and thus $dsttmp) contains '*'.
+
+ if [ -f "$src" ] || [ -d "$src" ]
+ then
+ :
+ else
+ echo "$0: $src does not exist" >&2
+ exit 1
+ fi
+
+ if [ x"$dst" = x ]
+ then
+ echo "$0: no destination specified" >&2
+ exit 1
+ else
+ :
+ fi
+
+# If destination is a directory, append the input filename; if your system
+# does not like double slashes in filenames, you may need to add some logic
+
+ if [ -d "$dst" ]
+ then
+ dst=$dst/`basename "$src"`
+ else
+ :
+ fi
+fi
+
+## this sed command emulates the dirname command
+dstdir=`echo "$dst" | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'`
+
+# Make sure that the destination directory exists.
+# this part is taken from Noah Friedman's mkinstalldirs script
+
+# Skip lots of stat calls in the usual case.
+if [ ! -d "$dstdir" ]; then
+defaultIFS='
+ '
+IFS="${IFS-$defaultIFS}"
+
+oIFS=$IFS
+# Some sh's can't handle IFS=/ for some reason.
+IFS='%'
+set - `echo "$dstdir" | sed -e 's@/@%@g' -e 's@^%@/@'`
+IFS=$oIFS
+
+pathcomp=''
+
+while [ $# -ne 0 ] ; do
+ pathcomp=$pathcomp$1
+ shift
+
+ if [ ! -d "$pathcomp" ] ;
+ then
+ $mkdirprog "$pathcomp"
+ else
+ :
+ fi
+
+ pathcomp=$pathcomp/
+done
+fi
+
+if [ x"$dir_arg" != x ]
+then
+ $doit $instcmd "$dst" &&
+
+ if [ x"$chowncmd" != x ]; then $doit $chowncmd "$dst"; else : ; fi &&
+ if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd "$dst"; else : ; fi &&
+ if [ x"$stripcmd" != x ]; then $doit $stripcmd "$dst"; else : ; fi &&
+ if [ x"$chmodcmd" != x ]; then $doit $chmodcmd "$dst"; else : ; fi
+else
+
+# If we're going to rename the final executable, determine the name now.
+
+ if [ x"$transformarg" = x ]
+ then
+ dstfile=`basename "$dst"`
+ else
+ dstfile=`basename "$dst" $transformbasename |
+ sed $transformarg`$transformbasename
+ fi
+
+# don't allow the sed command to completely eliminate the filename
+
+ if [ x"$dstfile" = x ]
+ then
+ dstfile=`basename "$dst"`
+ else
+ :
+ fi
+
+# Make a couple of temp file names in the proper directory.
+
+ dsttmp=$dstdir/_inst.$$_
+ rmtmp=$dstdir/_rm.$$_
+
+# Trap to clean up temp files at exit.
+
+ trap 'status=$?; rm -f "$dsttmp" "$rmtmp" && exit $status' 0
+ trap '(exit $?); exit' 1 2 13 15
+
+# Move or copy the file name to the temp name
+
+ $doit $instcmd "$src" "$dsttmp" &&
+
+# and set any options; do chmod last to preserve setuid bits
+
+# If any of these fail, we abort the whole thing. If we want to
+# ignore errors from any of these, just make sure not to ignore
+# errors from the above "$doit $instcmd $src $dsttmp" command.
+
+ if [ x"$chowncmd" != x ]; then $doit $chowncmd "$dsttmp"; else :;fi &&
+ if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd "$dsttmp"; else :;fi &&
+ if [ x"$stripcmd" != x ]; then $doit $stripcmd "$dsttmp"; else :;fi &&
+ if [ x"$chmodcmd" != x ]; then $doit $chmodcmd "$dsttmp"; else :;fi &&
+
+# Now remove or move aside any old file at destination location. We try this
+# two ways since rm can't unlink itself on some systems and the destination
+# file might be busy for other reasons. In this case, the final cleanup
+# might fail but the new file should still install successfully.
+
+{
+ if [ -f "$dstdir/$dstfile" ]
+ then
+ $doit $rmcmd -f "$dstdir/$dstfile" 2>/dev/null ||
+ $doit $mvcmd -f "$dstdir/$dstfile" "$rmtmp" 2>/dev/null ||
+ {
+ echo "$0: cannot unlink or rename $dstdir/$dstfile" >&2
+ (exit 1); exit
+ }
+ else
+ :
+ fi
+} &&
+
+# Now rename the file to the real destination.
+
+ $doit $mvcmd "$dsttmp" "$dstdir/$dstfile"
+
+fi &&
+
+# The final little trick to "correctly" pass the exit status to the exit trap.
+
+{
+ (exit 0); exit
+}
--- /dev/null
+-----BEGIN RSA PRIVATE KEY-----
+MIIBPQIBAAJBANNQC7sHwu9B33OIvC+hGIRXXQX3YAC1fWVmhUo/UMIzLTnCwqGN
+ZPtrWPRfCoLA+ltf+cElXlq+DPAbHLdINpcCAwEAAQJBAIsDe9QctQcFROUSrQqA
+ZkqZ1p6YMNYGj2nn2gQQRyaZphkw92cJOfO2Fco35rcrSMFPVVEUmds6oZX6uDNP
+SyECIQDv/F5mDHnhopFynlO2yccMIQ5e7B25Qd728TopHubnmQIhAOFp3AP9uzBt
+Q9H6zoHRHoHugU4RBnzH5sO39NqSyi2vAiEA6fc7vPfK15ybeog7C0iawwtzLD28
+GMHMcynozvAuo3kCIQCT5dkt5TUEuSxegaktFABoUA0XI6SWCrMmh3RoVRfCkQIh
+AK1nC9pyoManFdtDXSL6fdpaLz7gIlJwkz5hgPW43bwe
+-----END RSA PRIVATE KEY-----
--- /dev/null
+/*
+ * Copyright (C) 2004-2005 Andre Noll <maan@systemlinux.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+ */
+
+
+//#define PRINTF printf
+#define PRINTF(a, ...)
+
+#include <gkrellm2/gkrellm.h>
+#include <utime.h>
+#include <dirent.h>
+#include <errno.h>
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+
+static void create_para_ctrl(GtkWidget *, gint);
+static void update_para_ctrl(void);
+static void create_tab(GtkWidget *);
+static void apply_config(void);
+static void load_config(gchar *);
+static void save_config(FILE *);
+
+static GkrellmMonitor monitor = {
+ "Parakrell", /* Name, for config tab. */
+ 0, /* Id, 0 if a plugin */
+ create_para_ctrl, /* The create function */
+ update_para_ctrl, /* The update function */
+ create_tab, /* The config tab create function */
+ apply_config, /* Apply the config function */
+ save_config, /* Save user config */
+ load_config, /* Load user config */
+ "para_krell", /* config keyword */
+ NULL, /* Undefined 2 */
+ NULL, /* Undefined 1 */
+ NULL, /* Undefined 0 */
+ MON_APM, /* Insert plugin before this monitor */
+ NULL, /* Handle if a plugin, filled in by GKrellM */
+ NULL /* path if a plugin, filled in by GKrellM */
+};
+
+typedef struct {
+ GkrellmPiximage *image;
+ GkrellmDecalbutton *button;
+ gint x,y,w,h;
+ double x_scale,y_scale;
+} ControlButton;
+
+static ControlButton prev_button = {
+ .image = NULL,
+ .button = NULL,
+ .x = 10,
+ .y = 10,
+ .w = 50,
+ .h = 50,
+ .x_scale = 1,
+ .y_scale = 1,
+};
+
+GkrellmPiximage *piximage;
+GkrellmPanel *panel;
+GtkWidget *fileread_vbox;
+GdkPixbuf *pixbuf;
+gint song_change_input_id;
+FILE *song_change_fd;
+GIOChannel *song_change_channel;
+
+static struct timeval sc_open_time;
+
+static gchar *info_text =
+ "Parakrell displays an image corresponding to the soundfile\n"
+ "currently played by paraslash.\n\n"
+
+ "The plugin's panel is divided in 9 small squares, like \n"
+ "the numpad on a PC keyboard. For each square there is an \n"
+ "associated button and a number between 1 and 9.\n \n"
+
+ "Each button is bound to three different commands for left, \n"
+ "middle and right mouse button. Middle and right button are \n"
+ "reserved for setting volume and jumping around in the song, \n"
+ "respectively. Left button works as follows:\n \n"
+
+ "7 8 9 7: slider, 8: sdl_gui, 9: next \n"
+ "4 5 6 4: ps, 5: play, 6: dbadm \n"
+ "1 2 3 1: stop, 2: gui, 3: pause\n"
+
+ "\n\nAuthor:\n"
+ "Andre Noll <maan@systemlinux.org>\n"
+ "Copyright (C) 2004-2005\n"
+ "Distributed under the GNU General Public License.\n";
+
+#define MAXLINE 255
+
+static gboolean launch_cmd(char *cmd)
+{
+
+ gchar **argv;
+ GError *err = NULL;
+ gboolean res;
+
+ PRINTF("%s: \n", __func__);
+ if (!cmd || *cmd == '\0')
+ return -1;
+
+ g_shell_parse_argv(cmd, NULL, &argv, NULL);
+ res = g_spawn_async(
+ NULL,
+ argv,
+ NULL,
+ G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL,
+ NULL,
+ NULL,
+ NULL,
+ &err
+ );
+ if (!res && err) {
+ gkrellm_message_dialog(NULL, err->message);
+ g_error_free(err);
+ }
+ g_strfreev(argv);
+ return res;
+}
+
+static gboolean cb_in_button(GkrellmDecalbutton *b,
+ GdkEventButton *ev, ControlButton *cbut)
+{
+ gint x, y, area, width = gkrellm_chart_width();
+ x = (int) (3 * ev->x / width);
+ y = (int) (3 - 3 * ev->y / width);
+ area = 3 * y + x + 1;
+ char buf[MAXLINE];
+
+ PRINTF("%s: button %i pressed on area %i\n", __func__,
+ ev->button, area);
+ if (ev->button == 1) {
+ switch (area) {
+ case 1:
+ launch_cmd("para_client stop");
+ return 0;
+ case 2:
+ launch_cmd("xterm -e para_gui -a");
+ return 0;
+ case 3:
+ launch_cmd("para_client pause");
+ return 0;
+ case 4:
+ launch_cmd("para_client ps");
+ return 0;
+ case 5:
+ launch_cmd("para_client play");
+ return 0;
+ case 6:
+ launch_cmd("xterm -e para_dbadm");
+ return 0;
+ case 7:
+ launch_cmd("para_slider");
+ return 0;
+ case 8:
+ launch_cmd("para_sdl_gui -f");
+ return 0;
+ case 9:
+ launch_cmd("para_client next");
+ return 0;
+ }
+ return 0;
+ }
+ sprintf(buf, "%s %i", ev->button == 2? "aumix -v" : "para_client jmp",
+ area * 10);
+ return launch_cmd(buf);
+}
+
+static void make_button(ControlButton *cbut, gint fn_id)
+{
+ PRINTF("%s: gkrellm_make_scaled_button\n", __func__);
+ cbut->button = gkrellm_make_scaled_button(
+ panel,
+ cbut->image,
+ NULL,
+ GINT_TO_POINTER(fn_id),
+ FALSE,
+ FALSE,
+ 2,
+ 0,
+ 1,
+ cbut->x,
+ cbut->y,
+ cbut->w,
+ cbut->h
+ );
+ PRINTF("%s: making botton\n", __func__);
+ gkrellm_set_in_button_callback(cbut->button, cb_in_button, cbut);
+}
+
+
+static void load_img(void)
+{
+ gint width = gkrellm_chart_width();
+ gint out = 0, ret;
+ FILE *pipe = NULL;
+ char buf[MAXLINE];
+ size_t num_read = 0;
+ gchar *filename = gkrellm_make_data_file_name("para", "pic.jpg");
+
+ PRINTF("%s: Opening %s\n", __func__, filename);
+ if ((out = creat(filename, S_IRUSR | S_IWUSR)) < 0) {
+ perror("open");
+ goto out;
+ }
+ pipe = popen("para_client pic", "r");
+ if (!pipe)
+ goto out;
+ while ((ret = read(fileno(pipe), buf, sizeof(buf) - 1)) > 0) {
+ if (write(out, buf, ret) < 0) {
+ perror("Write");
+ goto out;
+ }
+ num_read += ret;
+ }
+ if (ret < 0) {
+ PRINTF("%s: Read Error\n", __func__);
+ goto out;
+ }
+ PRINTF("%s: new pic created (%i bytes)\n", __func__, num_read);
+ if (num_read < 500)
+ goto out;
+
+ if (piximage) {
+ gkrellm_destroy_piximage(piximage);
+ g_free(pixbuf);
+ }
+ PRINTF("%s: creating new piximage\n", __func__);
+ piximage = gkrellm_piximage_new_from_file(filename);
+ if (!piximage) {
+ PRINTF("%s: can not load image\n", __func__);
+ goto out;
+ }
+ pixbuf = gkrellm_scale_piximage_to_pixbuf(piximage, width, width);
+out:
+ g_free(filename);
+ if (pipe)
+ pclose(pipe);
+ if (out)
+ close(out);
+}
+
+static void create_tab(GtkWidget *tab_vbox)
+{
+ GtkTextBuffer *textbuf;
+ GtkWidget *text;
+ GtkWidget *scrolled, *vbox = tab_vbox;
+
+ PRINTF("%s: \n", __func__);
+
+ scrolled = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ textbuf = gtk_text_buffer_new(NULL);
+ gtk_text_buffer_set_text(textbuf, info_text, -1);
+ text = gtk_text_view_new_with_buffer(textbuf);
+ gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
+ gtk_container_add(GTK_CONTAINER(scrolled), text);
+ gtk_box_pack_start(GTK_BOX(vbox), scrolled, TRUE, TRUE, 0);
+}
+
+static gboolean song_change(GIOChannel *channel, GIOCondition condition,
+ gpointer panel)
+{
+ char *str_return;
+ gsize length;
+
+ PRINTF("%s: input condition %i "
+ "(song_change channel fd = %i)\n", __func__, condition,
+ g_io_channel_unix_get_fd(song_change_channel));
+ if (!song_change_fd) {
+ PRINTF("%s: no song_change_fd\n", __func__);
+ goto err_out;
+ }
+ if (!(condition & (G_IO_IN | G_IO_PRI))) {
+ PRINTF("%s: song change pipe died\n", __func__);
+ song_change_fd = NULL;
+ wait(NULL);
+ return TRUE;
+ goto err_out;
+ }
+ if (!channel->is_readable) {
+ PRINTF("%s: fd not readable\n", __func__);
+ goto err_out;
+ }
+ PRINTF("%s: reading data\n", __func__);
+ if (g_io_channel_read_line(channel, &str_return,
+ &length, NULL, NULL) == G_IO_STATUS_NORMAL) {
+ PRINTF("%s: next song: %s", __func__, str_return);
+ g_free(str_return);
+ if (channel != song_change_channel)
+ goto err_out;
+ load_img();
+ return TRUE;
+ }
+err_out:
+ g_io_channel_unref(channel);
+ g_io_channel_shutdown(channel, TRUE, NULL);
+ return FALSE;
+}
+
+static void create_song_change(void)
+{
+ if (song_change_fd)
+ return;
+ gettimeofday(&sc_open_time, NULL);
+ PRINTF("%s: para_client sc\n", __func__);
+ song_change_fd = popen("para_client sc", "r");
+ if (!song_change_fd) {
+ PRINTF("%s: para_client sc failed\n", __func__);
+ return;
+ }
+ song_change_channel = g_io_channel_unix_new(fileno(song_change_fd));
+ g_io_channel_set_close_on_unref(song_change_channel, TRUE);
+ g_io_add_watch(song_change_channel,
+ G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ &song_change, panel);
+}
+
+static void update_para_ctrl(void)
+{
+ GdkDrawable *drawable = panel->drawing_area->window;
+ gint width = gkrellm_chart_width();
+
+ if (!drawable)
+ PRINTF("%s: No drawable\n", __func__);
+ else
+ gkrellm_paste_pixbuf(pixbuf, drawable, 0, 0, width, width);
+ if (!song_change_fd) {
+ struct timeval now;
+ PRINTF("%s: no song_change_fd\n", __func__);
+
+ gettimeofday(&now, NULL);
+ if (now.tv_sec > sc_open_time.tv_sec + 5)
+ create_song_change();
+ }
+}
+
+static void destroy_panel(void)
+{
+ PRINTF("%s: \n", __func__);
+ if (!panel)
+ return;
+ PRINTF("%s: destroying panel\n", __func__);
+ gkrellm_panel_destroy(panel);
+ panel = NULL;
+}
+
+#if 0
+static void destroy_song_change(void)
+{
+ PRINTF("%s: \n", __func__);
+ if (!song_change_fd)
+ return;
+}
+#endif
+static void destroy_all(void)
+{
+// destroy_song_change();
+ destroy_panel();
+}
+
+static void create_para_ctrl(GtkWidget *vbox, gint first_create)
+{
+ gint style_id = gkrellm_lookup_meter_style_id(UPTIME_STYLE_NAME);
+ GkrellmStyle *style = gkrellm_meter_style(style_id);
+ gint width = gkrellm_chart_width();
+
+ destroy_all();
+ create_song_change();
+ panel = gkrellm_panel_new0();
+ gkrellm_panel_configure(panel, NULL, style);
+ gkrellm_panel_configure_set_height(panel, width);
+ PRINTF("%s: creating panel\n", __func__);
+ gkrellm_panel_create(vbox, &monitor, panel);
+ make_button(&prev_button, 1);
+}
+
+static void apply_config(void)
+{
+ PRINTF("%s: \n", __func__);
+}
+
+static void load_config(gchar * arg)
+{
+ PRINTF("%s: \n", __func__);
+
+}
+
+static void save_config(FILE * f)
+{
+ PRINTF("%s: \n", __func__);
+}
+
+GkrellmMonitor *gkrellm_init_plugin(void)
+{
+ return &monitor;
+}
--- /dev/null
+/*
+ * Copied from the Linux kernel source tree, version 2.6.13.
+ *
+ * Licensed under the GPL v2 as per the whole kernel source tree.
+ *
+ */
+
+#include <stddef.h> /* offsetof */
+#ifndef _LINUX_LIST_H
+#define _LINUX_LIST_H
+
+#define container_of(ptr, type, member) ({ \
+ const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+ (type *)( (char *)__mptr - offsetof(type,member) );})
+
+/*
+ * These are non-NULL pointers that will result in page faults
+ * under normal circumstances, used to verify that nobody uses
+ * non-initialized list entries.
+ */
+#define LIST_POISON1 ((void *) 0x00100100)
+#define LIST_POISON2 ((void *) 0x00200200)
+
+/*
+ * Simple doubly linked list implementation.
+ *
+ * Some of the internal functions ("__xxx") are useful when
+ * manipulating whole lists rather than single entries, as
+ * sometimes we already know the next/prev entries and we can
+ * generate better code by using them directly rather than
+ * using the generic single-entry routines.
+ */
+
+struct list_head {
+ struct list_head *next, *prev;
+};
+
+#define INIT_LIST_HEAD(ptr) do { \
+ (ptr)->next = (ptr); (ptr)->prev = (ptr); \
+} while (0)
+
+/*
+ * Insert a new entry between two known consecutive entries.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_add(struct list_head *new,
+ struct list_head *prev,
+ struct list_head *next)
+{
+ next->prev = new;
+ new->next = next;
+ new->prev = prev;
+ prev->next = new;
+}
+
+/**
+ * list_add - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it after
+ *
+ * Insert a new entry after the specified head.
+ * This is good for implementing stacks.
+ */
+static inline void list_add(struct list_head *new, struct list_head *head)
+{
+ __list_add(new, head, head->next);
+}
+
+/**
+ * list_add_tail - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it before
+ *
+ * Insert a new entry before the specified head.
+ * This is useful for implementing queues.
+ */
+static inline void list_add_tail(struct list_head *new, struct list_head *head)
+{
+ __list_add(new, head->prev, head);
+}
+
+/*
+ * Delete a list entry by making the prev/next entries
+ * point to each other.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_del(struct list_head * prev, struct list_head * next)
+{
+ next->prev = prev;
+ prev->next = next;
+}
+
+/**
+ * list_del - deletes entry from list.
+ * @entry: the element to delete from the list.
+ * Note: list_empty on entry does not return true after this, the entry is
+ * in an undefined state.
+ */
+static inline void list_del(struct list_head *entry)
+{
+ __list_del(entry->prev, entry->next);
+ entry->next = LIST_POISON1;
+ entry->prev = LIST_POISON2;
+}
+
+/**
+ * list_move - delete from one list and add as another's head
+ * @list: the entry to move
+ * @head: the head that will precede our entry
+ */
+static inline void list_move(struct list_head *list, struct list_head *head)
+{
+ __list_del(list->prev, list->next);
+ list_add(list, head);
+}
+
+/**
+ * list_empty - tests whether a list is empty
+ * @head: the list to test.
+ */
+static inline int list_empty(const struct list_head *head)
+{
+ return head->next == head;
+}
+
+/**
+ * list_entry - get the struct for this entry
+ * @ptr: the &struct list_head pointer.
+ * @type: the type of the struct this is embedded in.
+ * @member: the name of the list_struct within the struct.
+ */
+#define list_entry(ptr, type, member) \
+ container_of(ptr, type, member)
+
+/**
+ * list_for_each_safe - iterate over a list safe against removal of list entry
+ * @pos: the &struct list_head to use as a loop counter.
+ * @n: another &struct list_head to use as temporary storage
+ * @head: the head for your list.
+ */
+#define list_for_each_safe(pos, n, head) \
+ for (pos = (head)->next, n = pos->next; pos != (head); \
+ pos = n, n = pos->next)
+
+/**
+ * list_for_each_entry - iterate over list of given type
+ * @pos: the type * to use as a loop counter.
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ */
+#define list_for_each_entry(pos, head, member) \
+ for (pos = list_entry((head)->next, typeof(*pos), member); \
+ &pos->member != (head); \
+ pos = list_entry(pos->member.next, typeof(*pos), member))
+
+/**
+ * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
+ * @pos: the type * to use as a loop counter.
+ * @n: another type * to use as temporary storage
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_safe(pos, n, head, member) \
+ for (pos = list_entry((head)->next, typeof(*pos), member), \
+ n = list_entry(pos->member.next, typeof(*pos), member); \
+ &pos->member != (head); \
+ pos = n, n = list_entry(n->member.next, typeof(*n), member))
+#endif /* _LIST_H */
--- /dev/null
+/*
+ * Copyright (C) 2003-2006 Andre Noll <maan@systemlinux.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+ */
+
+/** \file mp3.c para_server's mp3 audio format handler */
+
+/*
+ * This file is based in part on mp3tech.c and mp3tech.h, Copyright (C)
+ * 2000-2001 Cedric Tefft <cedric@earthling.net>, which in turn is based
+ * in part on
+ *
+ * * MP3Info 0.5 by Ricardo Cerqueira <rmc@rccn.net>
+ * * MP3Stat 0.9 by Ed Sweetman <safemode@voicenet.com> and
+ * Johannes Overmann <overmann@iname.com>
+ */
+
+#include "server.cmdline.h"
+#include "server.h"
+#include "afs.h"
+#include "gcc-compat.h"
+#include "error.h"
+
+/** \cond some defines and structs which are only used in this file */
+
+/*
+ * MIN_CONSEC_GOOD_FRAMES defines how many consecutive valid MP3 frames we need
+ * to see before we decide we are looking at a real MP3 file
+ */
+#define MIN_CONSEC_GOOD_FRAMES 3
+
+#define FRAME_HEADER_SIZE 4
+#define MIN_FRAME_SIZE 21
+
+struct mp3header {
+ unsigned long sync;
+ unsigned int version;
+ unsigned int layer;
+ unsigned int crc;
+ unsigned int bitrate;
+ unsigned int freq;
+ unsigned int padding;
+ unsigned int mode;
+ unsigned int copyright;
+ unsigned int original;
+ unsigned int emphasis;
+};
+
+struct id3tag {
+ char title[31];
+ char artist[31];
+ char album[31];
+ char year[5];
+ char comment[31];
+};
+
+struct mp3info {
+ char *filename;
+ FILE *file;
+ struct mp3header header;
+ int id3_isvalid;
+ struct id3tag id3;
+ int vbr;
+ long unsigned br_average;
+ long unsigned seconds;
+ int frames;
+ int freq;
+};
+
+/** \endcond */
+static int frequencies[3][4] = {
+ {22050,24000,16000,50000}, /* MPEG 2.0 */
+ {44100,48000,32000,50000}, /* MPEG 1.0 */
+ {11025,12000,8000,50000} /* MPEG 2.5 */
+};
+
+static int mp3info_bitrate[2][3][14] = {
+{ /* MPEG 2.0 */
+ {32,48,56,64,80,96,112,128,144,160,176,192,224,256}, /* layer 1 */
+ {8,16,24,32,40,48,56,64,80,96,112,128,144,160}, /* layer 2 */
+ {8,16,24,32,40,48,56,64,80,96,112,128,144,160} /* layer 3 */
+},
+
+{ /* MPEG 1.0 */
+ {32,64,96,128,160,192,224,256,288,320,352,384,416,448}, /* layer 1 */
+ {32,48,56,64,80,96,112,128,160,192,224,256,320,384}, /* layer 2 */
+ {32,40,48,56,64,80,96,112,128,160,192,224,256,320} /* layer 3 */
+}
+};
+
+static int frame_size_index[] = {24000, 72000, 72000};
+static char *mode_text[] = {"stereo", "joint stereo", "dual channel", "mono", "invalid"};
+
+static struct mp3info mp3;
+static char mp3buf[8192];
+static int chunk_size;
+static struct audio_format *af;
+
+static __must_check int para_fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
+{
+ size_t res = fread(ptr, size, nmemb, stream);
+ if (res == nmemb)
+ return size * nmemb;
+ if (feof(stream))
+ return 0;
+ return -E_FREAD;
+}
+
+static int header_frequency(struct mp3header *h)
+{
+ if (h->version > 2 || h->freq > 3)
+ return -E_HEADER_FREQ;
+ return frequencies[h->version][h->freq];
+}
+
+static char *header_mode(struct mp3header *h)
+{
+ if (h->mode > 4)
+ h->mode = 4; /* invalid */
+ return mode_text[h->mode];
+}
+static int header_bitrate(struct mp3header *h)
+{
+ if (h->layer > 3 || h->bitrate > 14)
+ return -E_HEADER_BITRATE;
+ return mp3info_bitrate[h->version & 1][3 - h->layer][h->bitrate - 1];
+}
+
+static int frame_length(struct mp3header *header)
+{
+ int hb, hf = header_frequency(header);
+
+ if (hf < 0)
+ return hf;
+ hb = header_bitrate(header);
+ if (hb < 0)
+ return hb;
+ if (header->sync != 0xFFE || header->layer > 3)
+ return -E_FRAME;
+ return frame_size_index[3 - header->layer] *
+ ((header->version & 1) + 1) * hb / hf
+ + header->padding;
+}
+
+static void write_info_str(char *info_str)
+{
+ int v = mp3.id3_isvalid;
+
+ snprintf(info_str, MMD_INFO_SIZE,
+ "audio_file_info1:%d x %lu, %lu kbit/s (%cbr) %i KHz %s\n"
+ "audio_file_info2:%s, by %s\n"
+ "audio_file_info3:A: %s, Y: %s, C: %s\n",
+ mp3.frames,
+ af->chunk_tv.tv_usec,
+ mp3.br_average,
+ mp3.vbr? 'v' : 'c',
+ mp3.freq / 1000,
+ header_mode(&mp3.header),
+ v && *mp3.id3.title? mp3.id3.title : "(title tag not set)",
+ v && *mp3.id3.artist? mp3.id3.artist : "(artist tag not set)",
+ v && *mp3.id3.album? mp3.id3.album : "(album tag not set)",
+ v && *mp3.id3.year? mp3.id3.year : "????",
+ v && *mp3.id3.comment? mp3.id3.comment : "(comment tag not set)"
+ );
+}
+
+/*
+ * Remove trailing whitespace from the end of a string
+ */
+static char *unpad(char *string)
+{
+ char *pos = string + strlen(string) - 1;
+ while (isspace(pos[0]))
+ (pos--)[0] = 0;
+ return string;
+}
+
+static int compare_headers(struct mp3header *h1,struct mp3header *h2)
+{
+ if ((*(uint*)h1) == (*(uint*)h2))
+ return 1;
+ if ((h1->version == h2->version) &&
+ (h1->layer == h2->layer) &&
+ (h1->crc == h2->crc) &&
+ (h1->freq == h2->freq) &&
+ (h1->mode == h2->mode) &&
+ (h1->copyright == h2->copyright) &&
+ (h1->original == h2->original) &&
+ (h1->emphasis == h2->emphasis))
+ return 1;
+ else
+ return 0;
+}
+
+/**
+ * get next MP3 frame header.
+ *
+ * \param stream to read the header from
+ * \param header structure that gets filled in by get_header()
+ *
+ * \return On success, the header frame length is returned. A return value of
+ * zero means that we did not retrieve a valid frame header, and a negative
+ * return value indicates an error.
+ */
+static int get_header(FILE *file, struct mp3header *header)
+{
+ unsigned char buffer[FRAME_HEADER_SIZE];
+ int fl, ret;
+
+ if (!file || !header)
+ return -E_MP3_NO_FILE;
+ ret = para_fread(buffer, FRAME_HEADER_SIZE, 1, file);
+ if (ret < FRAME_HEADER_SIZE) {
+ header->sync = 0;
+ return ret < 0? ret : 0;
+ }
+ header->layer = (buffer[1] >> 1) & 3;
+ header->sync = (((int)buffer[0]<<4) | ((int)(buffer[1]&0xE0)>>4));
+ if (buffer[1] & 0x10)
+ header->version = (buffer[1] >> 3) & 1;
+ else
+ header->version = 2;
+ if ((header->sync != 0xFFE) || (header->layer != 1)) {
+ header->sync = 0;
+// PARA_DEBUG_LOG("%s: header not found\n", __func__);
+ return 0;
+ }
+ header->crc = buffer[1] & 1;
+ header->bitrate = (buffer[2] >> 4) & 0x0F;
+// PARA_DEBUG_LOG("%s: found header, bitrate: %u\n", __func__,
+// header->bitrate);
+ header->freq = (buffer[2] >> 2) & 0x3;
+ header->padding = (buffer[2] >>1) & 0x1;
+ header->mode = (buffer[3] >> 6) & 0x3;
+ fl = frame_length(header);
+ return (fl >= MIN_FRAME_SIZE)? fl : -E_FRAME_LENGTH;
+}
+
+/**
+ * find the next mp3 header
+ *
+ * \return On success, the length of the next frame header. If the end of the
+ * file was reached, the function returns zero. On errors, a negative value is
+ * returned.
+ *
+ */
+static int mp3_seek_next_header(void)
+{
+ int k, l = 0, c, first_len;
+ struct mp3header h, h2;
+ long valid_start = 0;
+
+ while (1) {
+ while ((c = fgetc(mp3.file)) != 255 && (c != EOF))
+ ; /* nothing */
+ if (c != 255)
+ return 0;
+ ungetc(c, mp3.file);
+ valid_start = ftell(mp3.file);
+ first_len = get_header(mp3.file, &h);
+ if (first_len <= 0)
+ continue;
+ if (fseek(mp3.file, first_len - FRAME_HEADER_SIZE, SEEK_CUR) < 0)
+ return -E_FSEEK;
+ for (k = 1; k < MIN_CONSEC_GOOD_FRAMES; k++) {
+ if ((l = get_header(mp3.file, &h2)) <= 0)
+ break;
+ if (!compare_headers(&h, &h2))
+ break;
+ fseek(mp3.file, l - FRAME_HEADER_SIZE, SEEK_CUR);
+ }
+ if (k == MIN_CONSEC_GOOD_FRAMES) {
+ fseek(mp3.file, valid_start, SEEK_SET);
+ memcpy(&(mp3.header), &h2, sizeof(struct mp3header));
+ return first_len;
+ }
+ }
+}
+
+static int mp3_get_id3(void)
+{
+ char fbuf[4];
+
+ mp3.id3_isvalid = 0;
+ mp3.id3.title[0] = '\0';
+ mp3.id3.artist[0] = '\0';
+ mp3.id3.album[0] = '\0';
+ mp3.id3.comment[0] = '\0';
+ mp3.id3.year[0] = '\0';
+ if (fseek(mp3.file, -128, SEEK_END))
+ return -E_FSEEK;
+ if (para_fread(fbuf, 1, 3, mp3.file) < 0)
+ return -E_FREAD;
+ fbuf[3] = '\0';
+ if (strcmp("TAG", fbuf)) {
+ PARA_INFO_LOG("%s", "no id3 tag\n");
+ return 0;
+ }
+ if (fseek(mp3.file, -125, SEEK_END) < 0)
+ return -E_FSEEK;
+ if (para_fread(mp3.id3.title, 1, 30, mp3.file) != 30)
+ return -E_FREAD;
+ mp3.id3.title[30] = '\0';
+ if (para_fread(mp3.id3.artist, 1, 30, mp3.file) != 30)
+ return -E_FREAD;
+ mp3.id3.artist[30] = '\0';
+ if (para_fread(mp3.id3.album, 1, 30, mp3.file) != 30)
+ return -E_FREAD;
+ mp3.id3.album[30] = '\0';
+ if (para_fread(mp3.id3.year, 1, 4, mp3.file) != 4)
+ return -E_FREAD;
+ mp3.id3.year[4] = '\0';
+ if (para_fread(mp3.id3.comment, 1, 30, mp3.file) != 30)
+ return -E_FREAD;
+ mp3.id3.comment[30] = '\0';
+ mp3.id3_isvalid = 1;
+ unpad(mp3.id3.title);
+ unpad(mp3.id3.artist);
+ unpad(mp3.id3.album);
+ unpad(mp3.id3.year);
+ unpad(mp3.id3.comment);
+ return 1;
+}
+
+static int find_valid_start(void)
+{
+ int frame_len;
+
+ if (!mp3.file)
+ return -E_MP3_NO_FILE;
+ frame_len = get_header(mp3.file, &mp3.header);
+ if (frame_len < 0)
+ return frame_len;
+ if (!frame_len) {
+ frame_len = mp3_seek_next_header();
+ if (frame_len <= 0)
+ return frame_len;
+ } else
+ if (fseek(mp3.file, -FRAME_HEADER_SIZE, SEEK_CUR) < 0)
+ return -E_FSEEK;
+ if (frame_len <= 1) /* FRAME_HEADER_SIZE? */
+ return -E_FRAME_LENGTH;
+ return frame_len;
+}
+
+static int mp3_read_info(void)
+{
+ long fl_avg = 0, freq_avg = 0, br_avg = 0, fcount = 0;
+ int ret, len = 0, old_br = -1;
+ struct timeval total_time = {0, 0};
+
+ ret = mp3_get_id3();
+ if (ret < 0)
+ return ret;
+ rewind(mp3.file);
+ mp3.vbr = 0;
+ mp3.freq = 0;
+ while (1) {
+ int freq, br, fl;
+ struct timeval tmp, cct; /* current chunk time */
+ if (len > 0)
+ if (fseek(mp3.file, len, SEEK_CUR) < 0)
+ return -E_FSEEK;
+ len = find_valid_start();
+ if (len <= 0)
+ break;
+ freq = header_frequency(&mp3.header);
+ br = header_bitrate(&mp3.header);
+ fl = frame_length(&mp3.header);
+ if (freq < 0 || br < 0 || fl < 0)
+ continue;
+ tmp.tv_sec = fl;
+ tmp.tv_usec = 0;
+ tv_divide(br * 125, &tmp, &cct);
+ tv_add(&cct, &total_time, &tmp);
+ total_time = tmp;
+// PARA_DEBUG_LOG("%s: br: %d, freq: %d, fl: %d, cct: %lu\n", __func__, br, freq, fl, cct.tv_usec);
+ fcount++;
+ if (fcount == 1) {
+ freq_avg = freq;
+ br_avg = br;
+ old_br = br;
+ fl_avg = fl;
+ continue;
+ }
+ freq_avg += (freq - freq_avg) / (fcount + 1);
+ fl_avg += (fl - fl_avg) / (fcount + 1);
+ br_avg += (br - br_avg) / (fcount + 1);
+ if (old_br != br)
+ mp3.vbr = 1;
+ old_br = br;
+ }
+ if (!fcount || !freq_avg || !br_avg)
+ return -E_MP3_INFO;
+ mp3.br_average = br_avg;
+ mp3.freq = freq_avg;
+ mp3.frames = fcount;
+ mp3.seconds = (tv2ms(&total_time) + 500) / 1000;
+ tv_divide(fcount, &total_time, &af->chunk_tv);
+ rewind(mp3.file);
+ PARA_DEBUG_LOG("chunk_time: %lu\n", af->chunk_tv.tv_usec);
+ tv_scale(10, &af->chunk_tv, &af->eof_tv);
+ return 1;
+}
+
+/*
+ * Read mp3 information from audio file
+ */
+static int mp3_get_file_info(FILE *audio_file, char *info_str,
+ long unsigned *frames, int *seconds)
+{
+ int ret;
+
+ if (!audio_file)
+ return -E_MP3_NO_FILE;
+ mp3.file = audio_file;
+ ret = mp3_read_info();
+ if (ret < 0) {
+ mp3.file = NULL;
+ return ret;
+ }
+ write_info_str(info_str);
+ *frames = mp3.frames;
+ *seconds = mp3.seconds;
+ if (*seconds < 2 || !*frames)
+ return -E_MP3_INFO;
+ return 1;
+}
+
+static int mp3_reposition_stream(long unsigned new_frame)
+{
+ int count = 0, len;
+
+ PARA_DEBUG_LOG("jmp to frame %lu/%i\n", new_frame, mp3.frames);
+ rewind(mp3.file);
+ while (count < new_frame && (len = find_valid_start()) > 0) {
+// PARA_DEBUG_LOG("%s: jmp to frame %d\n", __func__, count);
+ if (fseek(mp3.file, len, SEEK_CUR) < 0)
+ return -E_FSEEK;
+ count++;
+ }
+ if (count != new_frame) {
+ rewind(mp3.file);
+ return -E_MP3_REPOS;
+ }
+ return 1;
+}
+
+static int mp3_read_next_chunk(void)
+{
+ int len = find_valid_start();
+
+ if (len <= 0) {
+ if (len < 0)
+ PARA_ERROR_LOG("invalid frame len (%d)\n", len);
+ return len;
+ }
+ chunk_size = para_fread(mp3buf, len, 1, mp3.file);
+ if (len != chunk_size)
+ PARA_DEBUG_LOG("short read (%d/%d)\n", chunk_size, len);
+ return chunk_size;
+}
+
+static char *mp3_read_chunk(__unused long unsigned chunk_num, ssize_t *len)
+{
+ *len = mp3_read_next_chunk();
+ if (*len <= 0)
+ return NULL;
+ return mp3buf;
+}
+
+static void mp3_close_audio_file(void)
+{
+ if (!mp3.file)
+ return;
+ fclose(mp3.file);
+ mp3.file = NULL;
+}
+
+void mp3_init(void *p)
+{
+ af = p;
+ af->get_file_info = mp3_get_file_info;
+ af->reposition_stream = mp3_reposition_stream;
+ af->read_chunk = mp3_read_chunk;
+ af->close_audio_file = mp3_close_audio_file;
+ af->get_header_info = NULL;
+ /* eof_tv gets overwritten in mp3_get_file_info() */
+ af->eof_tv.tv_sec = 0;
+ af->eof_tv.tv_usec = 100 * 1000;
+}
--- /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 mp3dec.c paraslash's mp3 decoder */
+
+#include "gcc-compat.h"
+#include "para.h"
+
+#include "list.h"
+#include "filter.h"
+#include "error.h"
+#include <mad.h>
+#include "string.h"
+
+/** the output buffer size */
+#define MP3_OUTBUF_SIZE 128 * 1024
+
+/** \cond a helper macro */
+#define MAD_TO_SHORT(f) (f) >= MAD_F_ONE? SHRT_MAX :\
+ (f) <= -MAD_F_ONE? -SHRT_MAX : (signed short) ((f) >> (MAD_F_FRACBITS - 15))
+/** \endcond */
+
+/**
+ * data specific to the mp3dec filter
+ *
+ * \sa filter, filter_node
+ */
+struct private_mp3dec_data {
+ /** information on the current mp3 stream */
+ struct mad_stream stream;
+ /** information about the frame which is currently decoded */
+ struct mad_frame frame;
+ /** contains the PCM output */
+ struct mad_synth synth;
+};
+
+static ssize_t mp3dec(char *inbuffer, size_t len, struct filter_node *fn)
+{
+ int i, ret;
+ struct private_mp3dec_data *pmd = fn->private_data;
+ size_t copy = MIN(len, 4096);
+
+ if (fn->loaded > fn->bufsize * 4 / 5)
+ return 0;
+ mad_stream_buffer(&pmd->stream, (unsigned char *) inbuffer, copy);
+ pmd->stream.error = 0;
+next_frame:
+ ret = mad_frame_decode(&pmd->frame, &pmd->stream);
+ if (ret) {
+ if (MAD_RECOVERABLE(pmd->stream.error) || pmd->stream.error == MAD_ERROR_BUFLEN)
+ goto out;
+ PARA_ERROR_LOG("fatal: ret = %d, loaded = %d\n", ret, fn->loaded);
+ return -E_MAD_FRAME_DECODE;
+ }
+ mad_synth_frame(&pmd->synth, &pmd->frame);
+ fn->fci->samplerate = pmd->frame.header.samplerate;
+ fn->fci->channels = MAD_NCHANNELS(&pmd->frame.header);
+
+ for (i = 0; i < pmd->synth.pcm.length; i++) {
+ /* output format: unsigned 16 bit little endian */
+ signed short s = MAD_TO_SHORT(pmd->synth.pcm.samples[0][i]);
+ fn->buf[fn->loaded++] = s & 0xff;
+ fn->buf[fn->loaded++] = s >> 8;
+ if (MAD_NCHANNELS(&pmd->frame.header) == 2) { /* stereo */
+ s = MAD_TO_SHORT(pmd->synth.pcm.samples[1][i]);
+ fn->buf[fn->loaded++] = s & 0xff;
+ fn->buf[fn->loaded++] = s >> 8;
+ }
+ if (fn->loaded != fn->bufsize) /* output buffer not full */
+ continue;
+ PARA_ERROR_LOG("output buffer full: %d\n", fn->loaded);
+ return -E_MP3DEC_OVERRUN;
+ }
+ if (fn->loaded <= fn->bufsize * 4 / 5)
+ goto next_frame;
+out:
+ if (pmd->stream.next_frame) { /* we still have some data */
+ size_t off = pmd->stream.bufend - pmd->stream.next_frame;
+ PARA_DEBUG_LOG("converted %d, %d input bytes, %d output bytes\n",
+ len - off, off, fn->loaded);
+ return copy - off;
+ }
+ return copy;
+}
+
+static void mp3dec_close(struct filter_node *fn)
+{
+ struct private_mp3dec_data *pmd = fn->private_data;
+
+ mad_synth_finish(&pmd->synth);
+ mad_frame_finish(&pmd->frame);
+ mad_stream_finish(&pmd->stream);
+
+ free(fn->buf);
+ fn->buf = NULL;
+ free(pmd);
+ fn->private_data = NULL;
+}
+
+static void mp3dec_open(struct filter_node *fn)
+{
+ fn->private_data = para_calloc(sizeof(struct private_mp3dec_data));
+ struct private_mp3dec_data *pmd = fn->private_data;
+
+ mad_stream_init(&pmd->stream);
+ mad_frame_init(&pmd->frame);
+ mad_synth_init(&pmd->synth);
+ fn->loaded = 0;
+ fn->bufsize = MP3_OUTBUF_SIZE;
+ fn->buf = para_calloc(fn->bufsize);
+}
+/**
+ * the init function of the mp3dec filter
+ *
+ * \sa filter::init
+ */
+void mp3dec_init(struct filter *f)
+{
+ f->open = mp3dec_open;
+ f->convert = mp3dec;
+ f->close = mp3dec_close;
+}
--- /dev/null
+/*
+ * Copyright (C) 1999-2006 Andre Noll <maan@systemlinux.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+ */
+
+/** \file mysql.c para_server's mysql-based database tool */
+
+/** \cond some internal constants */
+#define MEDIUM_BLOB_SIZE 16777220 /* (2**24 + 4) */
+#define BLOB_SIZE 65539 /* (2**16 + 3) */
+/** \endcond */
+#include "server.cmdline.h"
+#include "server.h"
+#include "afs.h"
+#include "db.h"
+#include <mysql/mysql.h>
+#include <mysql/mysql_version.h>
+#include "error.h"
+#include "net.h"
+#include "string.h"
+
+extern struct gengetopt_args_info conf;
+/** pointer to the shared memory area */
+extern struct misc_meta_data *mmd;
+
+static void *mysql_ptr = NULL;
+
+
+static int com_cam(int, int, char **);
+static int com_cdb(int, int, char **);
+static int com_cs(int, int, char **);
+static int com_da(int, int, char **);
+static int com_hist(int, int, char **);
+static int com_info(int, int, char **);
+static int com_laa(int, int, char **);
+static int com_last(int, int, char **);
+static int com_ls(int, int, char **);
+static int com_mbox(int, int, char **);
+static int com_mv(int, int, char **);
+static int com_na(int, int, char **);
+static int com_pic(int, int, char **);
+static int com_picch(int, int, char **);
+static int com_picdel(int, int, char **);
+static int com_piclist(int, int, char **);
+static int com_ps(int, int, char **);
+static int com_rm_ne(int, int, char **);
+static int com_sa(int, int, char **);
+static int com_set(int, int, char **);
+static int com_sl(int, int, char **);
+static int com_stradd_picadd(int, int, char **);
+static int com_streams(int, int, char **);
+static int com_strdel(int, int, char **);
+static int com_strq(int, int, char **);
+static int com_summary(int, int, char **);
+static int com_upd(int, int, char **);
+static int com_us(int, int, char **);
+static int com_verb(int, int, char **);
+static int com_vrfy(int, int, char **);
+
+static struct server_command cmds[] = {
+{
+.name = "cam",
+.handler = com_cam,
+.perms = DB_READ|DB_WRITE,
+.description = "copy all metadata",
+.synopsis = "cam source dest1 [dest2 ...]",
+.help =
+
+"Copy attributes and other meta data from source file to destination\n"
+"file(s). Useful for files that have been renamed.\n"
+
+},
+{
+.name = "cdb",
+.handler = com_cdb,
+.perms = DB_READ|DB_WRITE,
+.description = "create database",
+.synopsis = "cdb [name]",
+.help =
+
+"\tCreate database name containing the initial columns for basic\n"
+"\tinteroperation with server. This command has to be used only once\n"
+"\twhen you use the mysql database tool for the very first time.\n"
+"\n"
+"\tThe optional name defaults to 'paraslash' if not given.\n"
+
+},
+{
+.name = "clean",
+.handler = com_vrfy,
+.perms = DB_READ | DB_WRITE,
+.description = "nuke invalid entries in database",
+.synopsis = "clean",
+.help =
+
+"If the vrfy command shows you any invalid entries in your database,\n"
+"you can get rid of them with clean. Always run 'upd' and 'vrfy'\n"
+"before running this command. Use with caution!\n"
+
+},
+{
+.name = "cs",
+.handler = com_cs,
+.perms = AFS_WRITE | DB_READ | DB_WRITE,
+.description = "change stream",
+.synopsis = "cs [s]",
+.help =
+
+"Selects stream s or prints current stream when s was not given.\n"
+
+},
+{
+.name = "csp",
+.handler = com_cs,
+.perms = AFS_WRITE | DB_READ,
+.description = "change stream and play",
+.synopsis = "csp s",
+.help =
+
+"Select stream s and start playing. If this results in a stream-change,\n"
+"skip rest of current audio file.\n"
+
+},
+{
+.name = "da",
+.handler = com_da,
+.perms = DB_READ | DB_WRITE,
+.description = "drop attribute from database",
+.synopsis = "da att",
+.help =
+
+"Use with caution. All info on attribute att will be lost.\n"
+
+},
+{
+.name = "hist",
+.handler = com_hist,
+.perms = DB_READ,
+.description = "print history",
+.synopsis = "hist",
+.help =
+
+"Print list of all audio files together with number of days since each\n"
+"file was last played.\n"
+
+},
+{
+.name = "info",
+.handler = com_info,
+.perms = DB_READ,
+.description = "print database info",
+.synopsis = "info [af]",
+.help =
+
+"print database informations for audio file af. Current audio file is\n"
+"used if af is not given.\n"
+
+},
+{
+.name = "la",
+.handler = com_info,
+.perms = DB_READ,
+.description = "list attributes",
+.synopsis = "la [af]",
+.help =
+
+"List attributes of audio file af or of current audio file when invoked\n"
+"without arguments.\n"
+
+},
+{
+.name = "laa",
+.handler = com_laa,
+.perms = DB_READ,
+.description = "list available attributes",
+.synopsis = "laa",
+.help =
+
+"What should I say more?\n"
+
+},
+{
+.name = "last",
+.handler = com_last,
+.perms = DB_READ,
+.description = "print list of audio files, ordered by lastplayed time",
+.synopsis = "last [n]",
+.help =
+
+"The optional number n defaults to 10 if not specified.\n"
+
+},
+{
+.name = "ls",
+.handler = com_ls,
+.perms = DB_READ,
+.description = "list all audio files that match a LIKE pattern",
+.synopsis = "ls [pattern]",
+.help =
+
+"\tIf pattern was not given, print list of all audio files known\n"
+"\tto the mysql database tool. See the documentation of mysql\n"
+"\tfor the definition of LIKE patterns.\n"
+
+},
+{
+.name = "mbox",
+.handler = com_mbox,
+.perms = DB_READ,
+.description = "dump audio file list in mbox format",
+.synopsis = "mbox [p]",
+.help =
+
+"\tDump list of audio files in mbox format (email) to stdout. If\n"
+"\tthe optional pattern p is given, only those audio files,\n"
+"\twhose basename match p are going to be included. Otherwise,\n"
+"\tall files are selected.\n"
+"\n"
+"EXAMPLE\n"
+"\tThe mbox command can be used together with your favorite\n"
+"\tmailer (this example uses mutt) for browsing the audio file\n"
+"\tcollection:\n"
+"\n"
+"\t\tpara_client mbox > ~/para_mbox\n"
+"\n"
+"\t\tmutt -F ~/.muttrc.para -f ~/para_mbox\n"
+"\n"
+"\tFor playlists, you can use mutt's powerful pattern matching\n"
+"\tlanguage to select files. If you like to tag all files\n"
+"\tcontaining the pattern 'foo', type 'T', then '~s foo'.\n"
+"\n"
+"\tWhen ready with the list, type ';|' (i.e., hit the semicolon\n"
+"\tkey to apply the next mutt command to all tagged messages,\n"
+"\tthen the pipe key) to pipe the selected \"mails\" to a\n"
+"\tsuitable script which adds a paraslash stream where exactly\n"
+"\tthese files are admissable or does whatever thou wilt.\n"
+
+},
+{
+.name = "mv",
+.handler = com_mv,
+.perms = DB_READ | DB_WRITE,
+.description = "rename entry in database",
+.synopsis = "mv oldname newname",
+.help =
+
+"Rename oldname to newname. This updates the data table to reflect the\n"
+"new name. All internal data (numplayed, lastplayed, picid,..) is kept.\n"
+"If newname is a full path, the dir table is updated as well.\n"
+
+},
+{
+.name = "na",
+.handler = com_na,
+.perms = DB_READ | DB_WRITE,
+.description = "add new attribute to database",
+.synopsis = "na att",
+.help =
+
+"This adds a column named att to your mysql database. att should only\n"
+"contain letters and numbers, in paricular, '+' and '-' are not allowed.\n"
+
+},
+{
+.name = "ne",
+.handler = com_rm_ne,
+.perms = DB_READ | DB_WRITE,
+.description = "add new database entries",
+.synopsis = "ne file1 [file2 [...]]",
+.help =
+
+"Add the given filename(s) to the database, where file1,... must\n"
+"be full path names. This command might be much faster than 'upd'\n"
+"if the number of given files is small.\n"
+
+},
+{
+.name = "ns",
+.handler = com_ps,
+.perms = AFS_WRITE | DB_READ | DB_WRITE,
+.description = "change to next stream",
+.synopsis = "ns",
+.help =
+
+"Cycle forwards through stream list.\n"
+
+},
+{
+.name = "pic",
+.handler = com_pic,
+.perms = DB_READ,
+.description = "get picture by name or by identifier",
+.synopsis = "pic [name]",
+.help =
+
+"\tDump jpg image that is associated to given audio file (current\n"
+"\taudio file if not specified) to stdout. If name starts with\n"
+"\t'#' it is interpreted as an identifier instead and the picture\n"
+"\thaving that identifier is dumped to stdout.\n"
+"\n"
+"EXAMPLE\n"
+"\n"
+"\tpara_client pic '#123' > pic123.jpg\n"
+
+},
+{
+.name = "picadd",
+.handler = com_stradd_picadd,
+.perms = DB_READ | DB_WRITE,
+.description = "add picture to database",
+.synopsis = "picadd [picname]",
+.help =
+
+"\tRead jpeg file from stdin and store it as picname in database.\n"
+"\n"
+"EXAMPLE\n"
+"\n"
+"\tpara_client picadd foo.jpg < foo.jpg\n"
+
+},
+{
+.name = "picass",
+.handler = com_set,
+.perms = DB_READ | DB_WRITE,
+.description = "associate a picture to file(s)",
+.synopsis = "picass pic_id file1 [file2...]",
+.help =
+
+"Associate the picture given by pic_id to all given files.\n"
+
+},
+{
+.name = "picch",
+.handler = com_picch,
+.perms = DB_READ | DB_WRITE,
+.description = "change name of picture",
+.synopsis = "picch id new_name",
+.help =
+
+"Asign new_name to picture with identifier id.\n"
+
+},
+{
+.name = "picdel",
+.handler = com_picdel,
+.perms = DB_READ | DB_WRITE,
+.description = "delete picture from database",
+.synopsis = "picdel id1 [id2...]",
+.help =
+
+"Delete each given picture from database.\n"
+
+},
+{
+.name = "piclist",
+.handler = com_piclist,
+.perms = DB_READ,
+.description = "print list of pictures",
+.synopsis = "piclist",
+.help =
+
+"Print id, name and length of each picture contained in the database.\n"
+
+},
+{
+.name = "ps",
+.handler = com_ps,
+.perms = AFS_WRITE | DB_READ | DB_WRITE,
+.description = "change to previous stream",
+.synopsis = "ps",
+.help =
+
+"Cycle backwards through stream list.\n"
+
+},
+{
+.name = "rm",
+.handler = com_rm_ne,
+.perms = DB_READ | DB_WRITE,
+.description = "remove entries from database",
+.synopsis = "rm name1 [name2 [...]]",
+.help =
+
+"Remove name1, name2, ... from the data table. Use with caution\n"
+
+},
+{
+.name = "sa",
+.handler = com_sa,
+.perms = DB_READ | DB_WRITE,
+.description = "set/unset attributes",
+.synopsis = "sa at1<'+' | '-'> [at2<'+' | '-'> ] [af1 ...]",
+//.synopsis = "foo",
+.help =
+
+"Set ('+') or unset ('-') attribute at1, at2 etc. for given list of\n"
+"audio files. If no audio files were given the current audio file is\n"
+"used. Example:\n"
+"\n"
+"sa rock+ punk+ classic- LZ__Waldsterben.mp3\n"
+"\n"
+"sets the 'rock' and the 'punk' attribute but unsets the 'classic'\n"
+"attribute.\n"
+
+},
+{
+.name = "skip",
+.handler = com_sl,
+.perms = DB_READ | DB_WRITE,
+.description = "skip subsequent audio files(s)",
+.synopsis = "skip n [s]",
+.help =
+
+"Skip the next n audio files of stream s. This is equivalent to the\n"
+"command 'sl n s', followed by 'us name' for each name the output of sl.\n"
+
+},
+{
+.name = "sl",
+.handler = com_sl,
+.perms = DB_READ,
+.description = "print score list",
+.synopsis = "sl n [s]",
+.help =
+
+"Print sorted list of maximal n lines. Each line is an admissible entry\n"
+"with respect to stream s. The list is sorted by score-value which is\n"
+"given by the definition of s. If s is not given, the current stream\n"
+"is used. Example:\n"
+"\n"
+" sl 1\n"
+"\n"
+"shows you the audio file the server would select right now.\n"
+
+},
+{
+.name = "snp",
+.handler = com_set,
+.perms = DB_READ | DB_WRITE,
+.description = "set numplayed",
+.synopsis = "snp number af1 [af2 ...]",
+.help =
+
+"Update the numplayed field in the data table for all given audio files.\n"
+
+},
+{
+.name = "stradd",
+.handler = com_stradd_picadd,
+.perms = DB_READ | DB_WRITE,
+.description = "add stream",
+.synopsis = "stradd s",
+.help =
+
+"Add stream s to the list of available streams. The stream definition\n"
+"for s is read from stdin and is then sent to para_server. Example:\n"
+"\n"
+" echo 'deny: NAME_LIKE(%Madonna%)' | para_client stradd no_madonna\n"
+"\n"
+"adds the new stream 'no_madonna' to the list of available streams. A given\n"
+"audio file is admissible for this stream iff its basename does not contain the\n"
+"string 'Madonna'.\n"
+
+
+},
+{
+.name = "strdel",
+.handler = com_strdel,
+.perms = DB_READ | DB_WRITE,
+.description = "delete stream",
+.synopsis = "strdel s",
+.help =
+
+"Remove stream s from database.\n"
+
+},
+{
+.name = "streams",
+.handler = com_streams,
+.perms = DB_READ,
+.description = "list streams",
+.synopsis = "streams",
+.help =
+
+"Print list of available streams. Use 'cs' to switch to any of these.\n"
+
+},
+{
+.name = "strq",
+.handler = com_strq,
+.perms = DB_READ,
+.description = "query stream definition",
+.synopsis = "strq [s]",
+.help =
+
+"Print definition of stream s to stdout. Use current stream if s was\n"
+"not given.\n"
+
+},
+{
+.name = "summary",
+.handler = com_summary,
+.perms = DB_READ,
+.description = "list attributes",
+.synopsis = "summary",
+.help =
+
+"\tPrint a list of attributes together with number of audio\n"
+"\tfiles having that attribute set.\n"
+
+},
+{
+.name = "upd",
+.handler = com_upd,
+.perms = DB_READ | DB_WRITE,
+.description = "update database",
+.synopsis = "upd",
+.help =
+
+"This command uses the --audio_file_dir option of para_server to locate\n"
+"your audio files. New files are then added to the mysql database. Use\n"
+"this command if you got new files or if you have moved some files\n"
+"around.\n"
+
+},
+{
+.name = "us",
+.handler = com_us,
+.perms = DB_READ | DB_WRITE,
+.description = "update lastplayed time",
+.synopsis = "us name",
+.help =
+
+"Update lastplayed time without actually playing the thing.\n"
+
+},
+{
+.name = "verb",
+.handler = com_verb,
+.perms = DB_READ | DB_WRITE,
+.description = "send verbatim sql query",
+.synopsis = "verb cmd",
+.help =
+
+"Send cmd to mysql server. For expert/debugging only. Note that cmd\n"
+"usually must be escaped. Use only if you know what you are doing!\n"
+
+},
+{
+.name = "vrfy",
+.handler = com_vrfy,
+.perms = DB_READ,
+.description = "list invalid entries in database",
+.synopsis = "vrfy",
+.help =
+
+"Show what clean would delete. Run 'upd' before this command to make\n"
+"sure your database is up to date.\n"
+
+},
+{
+.name = NULL,
+}
+};
+
+static struct para_macro macro_list[] = {
+ { .name = "IS_N_SET",
+ .replacement = "(data.%s != '1')"
+ }, {
+ .name = "IS_SET",
+ .replacement = "(data.%s = '1')"
+ }, {
+ .name = "PICID",
+ .replacement = "%sdata.Pic_Id"
+ }, {
+ .name = "NAME_LIKE",
+ .replacement = "(data.name like '%s')"
+ }, {
+ .name = "LASTPLAYED",
+ .replacement = "%sFLOOR((UNIX_TIMESTAMP(now())"
+ "-UNIX_TIMESTAMP(data.Lastplayed))/60)"
+ }, {
+ .name = "NUMPLAYED",
+ .replacement = "%sdata.Numplayed"
+ }, {
+ .name = NULL,
+ }
+};
+
+static int real_query(char *query)
+{
+ if (!mysql_ptr)
+ return -E_NOTCONN;
+ PARA_DEBUG_LOG("%s\n", query);
+ if (mysql_real_query(mysql_ptr, query, strlen(query))) {
+ PARA_ERROR_LOG("real_query error (%s)\n",
+ mysql_error(mysql_ptr));
+ return -E_QFAILED;
+ }
+ return 1;
+}
+
+/*
+ * Use open connection given by mysql_ptr to query server. Returns a
+ * result pointer on succes and NULL on errors
+ */
+static struct MYSQL_RES *get_result(char *query)
+{
+ void *result;
+
+ if (real_query(query) < 0)
+ return NULL;
+ result = mysql_store_result(mysql_ptr);
+ if (!result)
+ PARA_ERROR_LOG("%s", "store_result error\n");
+ return result;
+}
+/*
+ * write input from fd to dynamically allocated char array,
+ * but maximal max_size byte. Return size.
+ */
+static int fd2buf(int fd, char **buf_ptr, size_t max_size)
+{
+ const size_t chunk_size = 1024;
+ size_t size = 2048;
+ char *buf = para_malloc(size * sizeof(char)), *p = buf;
+ int ret;
+
+ while ((ret = recv_bin_buffer(fd, p, chunk_size)) > 0) {
+ p += ret;
+ if ((p - buf) + chunk_size >= size) {
+ char *tmp;
+
+ size *= 2;
+ if (size > max_size) {
+ ret = -E_TOOBIG;
+ goto out;
+ }
+ tmp = para_realloc(buf, size);
+ p = (p - buf) + tmp;
+ buf = tmp;
+ }
+ }
+ if (ret < 0)
+ goto out;
+ *buf_ptr = buf;
+ ret = p - buf;
+out:
+ if (ret < 0 && buf)
+ free(buf);
+ return ret;
+}
+
+static char *escape_blob(char* old, int size)
+{
+ char *new;
+
+ if (!mysql_ptr || size < 0)
+ return NULL;
+ new = para_malloc(2 * size * sizeof(char) + 1);
+ mysql_real_escape_string(mysql_ptr, new, old, size);
+ return new;
+}
+
+static char *escape_str(char* old)
+{
+ return escape_blob(old, strlen(old));
+}
+
+static char *escaped_basename(const char *name)
+{
+ char *esc, *bn = para_basename(name);
+
+ if (!bn)
+ return NULL;
+ esc = escape_str(bn);
+ free(bn);
+ return esc;
+}
+
+/*
+ * new attribute
+ */
+static int com_na(__unused int fd, int argc, char *argv[])
+{
+ char *q;
+ int ret;
+
+ if (argc < 1)
+ return -E_MYSQL_SYNTAX;
+ q = make_message("alter table data add %s char(1) "
+ "not null default 0", argv[1]);
+ ret = real_query(q);
+ free(q);
+ return ret;
+}
+
+/*
+ * delete attribute
+ */
+static int com_da(__unused int fd, int argc, char *argv[])
+{
+ char *q;
+ int ret;
+
+ if (argc < 1)
+ return -E_MYSQL_SYNTAX;
+ q = make_message("alter table data drop %s", argv[1]);
+ ret = real_query(q);
+ free(q);
+ return ret;
+}
+
+/* stradd/pic_add */
+static int com_stradd_picadd(int fd, int argc, char *argv[])
+{
+ char *blob = NULL, *esc_blob = NULL, *q;
+ const char *fmt, *del_fmt;
+ int ret, stradd = strcmp(argv[0], "picadd");
+ size_t size;
+
+ if (argc < 1)
+ return -E_MYSQL_SYNTAX;
+ if (strlen(argv[1]) >= MAXLINE - 1)
+ return -E_NAMETOOLONG;
+ if (!mysql_ptr)
+ return -E_NOTCONN;
+ if (stradd) {
+ size = BLOB_SIZE;
+ fmt = "insert into streams (name, def) values ('%s','%s')";
+ del_fmt="delete from streams where name='%s'";
+ } else {
+ size = MEDIUM_BLOB_SIZE;
+ fmt = "insert into pics (name, pic) values ('%s','%s')";
+ del_fmt="delete from pics where pic='%s'";
+ }
+ q = make_message(del_fmt, argv[1]);
+ ret = real_query(q);
+ free(q);
+ if (ret < 0)
+ return ret;
+ if ((ret = send_buffer(fd, AWAITING_DATA_MSG) < 0))
+ return ret;
+ if ((ret = fd2buf(fd, &blob, size)) < 0)
+ return ret;
+ PARA_DEBUG_LOG("length: %i\n", ret);
+ size = ret;
+ if (stradd)
+ blob[size] = '\0';
+ esc_blob = escape_blob(blob, ret);
+ free(blob);
+ if (!esc_blob)
+ return -E_TOOBIG;
+ q = make_message(fmt, argv[1], esc_blob);
+ free(esc_blob);
+ ret = real_query(q);
+ free(q);
+ return ret;
+}
+
+/*
+ * print results to fd
+ */
+static int print_results(int fd, void *result,
+ unsigned int top, unsigned int left,
+ unsigned int bottom, unsigned int right)
+{
+ unsigned int i,j;
+ int ret;
+ MYSQL_ROW row;
+
+ for (i = top; i <= bottom; i++) {
+ row = mysql_fetch_row(result);
+ if (!row || !row[0])
+ return -E_NOROW;
+ for (j = left; j <= right; j++) {
+ ret = send_va_buffer(fd, j == left? "%s" : "\t%s",
+ row[j]? row[j] : "NULL");
+ if (ret < 0)
+ return ret;
+ }
+ ret = send_buffer(fd, "\n");
+ if (ret < 0)
+ return ret;
+ }
+ return 0;
+}
+
+/*
+ * verbatim
+ */
+static int com_verb(int fd, int argc, char *argv[])
+{
+ void *result = NULL;
+ int ret;
+ unsigned int num_rows, num_fields;
+
+ if (argc < 1)
+ return -E_MYSQL_SYNTAX;
+ result = get_result(argv[1]);
+ if (!result)
+ /* return success, because it's ok to have no results */
+ return 1;
+ num_fields = mysql_field_count(mysql_ptr);
+ num_rows = mysql_num_rows(result);
+ ret = 1;
+ if (num_fields && num_rows)
+ ret = print_results(fd, result, 0, 0, num_rows - 1,
+ num_fields - 1);
+ mysql_free_result(result);
+ return ret;
+}
+
+/* returns NULL on errors or if there are no atts defined yet */
+static void *get_all_attributes(void)
+{
+ void *result = get_result("desc data");
+ unsigned int num_rows;
+
+ if (!result)
+ return NULL;
+ num_rows = mysql_num_rows(result);
+ if (num_rows < 5) {
+ mysql_free_result(result);
+ return NULL;
+ }
+ mysql_data_seek(result, 4); /* skip Lastplayed, Numplayed... */
+ return result;
+}
+
+/*
+ * list all attributes
+ */
+static int com_laa(int fd, int argc, __unused char *argv[])
+{
+ void *result;
+ int ret;
+
+ if (argc)
+ return -E_MYSQL_SYNTAX;
+ result = get_all_attributes();
+ if (!result)
+ return -E_NOATTS;
+ ret = print_results(fd, result, 0, 0, mysql_num_rows(result) - 5, 0);
+ mysql_free_result(result);
+ return ret;
+}
+
+/*
+ * history
+ */
+static int com_hist(int fd, int argc, char *argv[]) {
+ int ret;
+ void *result = NULL;
+ char *q;
+ unsigned int num_rows;
+
+ q = make_message("select name, to_days(now()) - to_days(lastplayed) from "
+ "data%s%s%s order by lastplayed",
+ (argc < 1)? "" : " where ",
+ (argc < 1)? "" : argv[1],
+ (argc < 1)? "" : " = '1'");
+ result = get_result(q);
+ free(q);
+ if (!result)
+ return -E_NORESULT;
+ num_rows = mysql_num_rows(result);
+ ret = 1;
+ if (num_rows)
+ ret = print_results(fd, result, 0, 0, num_rows - 1, 1);
+ mysql_free_result(result);
+ return ret;
+}
+
+/*
+ * get last num audio files
+ */
+static int com_last(int fd, int argc, char *argv[])
+{
+ void *result = NULL;
+ char *q;
+ int num, ret;
+
+ if (argc < 1)
+ num = 10;
+ else
+ num = atoi(argv[1]);
+ if (!num)
+ return -E_MYSQL_SYNTAX;
+ q = make_message("select name from data order by lastplayed desc "
+ "limit %u", num);
+ result = get_result(q);
+ free(q);
+ if (!result)
+ return -E_NORESULT;
+ ret = print_results(fd, result, 0, 0, mysql_num_rows(result) - 1, 0);
+ mysql_free_result(result);
+ return ret;
+}
+
+static int com_mbox(int fd, int argc, char *argv[])
+{
+ void *result;
+ MYSQL_ROW row;
+ int ret;
+ unsigned int num_rows, num_fields;
+ char *query = para_strdup("select concat('From foo@localhost ', "
+ "date_format(Lastplayed, '%a %b %e %T %Y'), "
+ "'\nReceived: from\nTo: bar\n");
+
+ ret = -E_NOATTS;
+ result = get_all_attributes();
+ if (!result)
+ goto out;
+ ret = -E_NOROW;
+ while ((row = mysql_fetch_row(result))) {
+ char *tmp;
+
+ if (!row[0])
+ goto out;
+ tmp = make_message("%s X-Attribute-%s: ', %s, '\n", query,
+ row[0], row[0]);
+ free(query);
+ query = tmp;
+ }
+ query = para_strcat(query,
+ "From: a\n"
+ "Subject: "
+ "', name, '"
+ "\n\n\n"
+ "') from data"
+ );
+ if (argc >= 1) {
+ char *tmp = make_message("%s where name LIKE '%s'", query,
+ argv[1]);
+ free(query);
+ query = tmp;
+ }
+ mysql_free_result(result);
+ ret = -E_NORESULT;
+ result = get_result(query);
+ if (!result)
+ goto out;
+ ret = -E_EMPTY_RESULT;
+ num_fields = mysql_field_count(mysql_ptr);
+ num_rows = mysql_num_rows(result);
+ if (!num_fields || !num_rows)
+ goto out;
+ ret = print_results(fd, result, 0, 0, num_rows - 1, num_fields - 1);
+out:
+ free(query);
+ if (result)
+ mysql_free_result(result);
+ return ret;
+}
+
+/* get attributes by name. If verbose is not 0, get_a writes a string
+ * into atts of the form 'att1="0",att2="1"', which is used in com_cam
+ * for contructing a mysql update query.
+ * never returns NULL in *NON VERBOSE* mode
+ */
+static char *get_atts(char *name, int verbose)
+{
+ char *atts = NULL, *buf, *ebn;
+ void *result = NULL, *result2 = NULL;
+ MYSQL_ROW row, row2;
+ int i, ret;
+ unsigned int num_fields;
+
+ ret = -E_NOATTS;
+ result2 = get_all_attributes();
+ if (!result2)
+ goto out;
+ ret = -E_ESCAPE;
+ if (!(ebn = escaped_basename(name)))
+ goto out;
+ buf = make_message("select * from data where name='%s'", ebn);
+ free(ebn);
+ ret = -E_NORESULT;
+ result = get_result(buf);
+ free(buf);
+ if (!result)
+ goto out;
+ ret = -E_EMPTY_RESULT;
+ num_fields = mysql_num_fields(result);
+ if (num_fields < 5)
+ goto out;
+ mysql_data_seek(result2, 4); /* skip Lastplayed, Numplayed... */
+ row = mysql_fetch_row(result);
+ ret = -E_NOROW;
+ if (!row)
+ goto out;
+ for (i = 4; i < num_fields; i++) {
+ int is_set = row[i] && !strcmp(row[i], "1");
+ row2 = mysql_fetch_row(result2);
+ if (!row2 || !row2[0])
+ goto out;
+ if (atts && (verbose || is_set))
+ atts = para_strcat(atts, verbose? "," : " ");
+ if (is_set || verbose)
+ atts = para_strcat(atts, row2[0]);
+ if (verbose)
+ atts = para_strcat(atts, is_set? "=\"1\"" : "=\"0\"");
+ }
+ ret = 1;
+out:
+ if (result2)
+ mysql_free_result(result2);
+ if (result)
+ mysql_free_result(result);
+ if (!atts && !verbose)
+ atts = para_strdup("(none)");
+ return atts;
+}
+
+/* never returns NULL in verbose mode */
+static char *get_meta(char *name, int verbose)
+{
+ MYSQL_ROW row;
+ void *result = NULL;
+ char *ebn, *q, *ret = NULL;
+ const char *verbose_fmt =
+ "select concat('lastplayed: ', "
+ "(to_days(now()) - to_days(lastplayed)),"
+ "' day(s). numplayed: ', numplayed, "
+ "', pic: ', pic_id) "
+ "from data where name = '%s'";
+ /* is that really needed? */
+ const char *fmt = "select concat('lastplayed=\\'', lastplayed, "
+ "'\\', numplayed=\\'', numplayed, "
+ "'\\', pic_id=\\'', pic_id, '\\'') "
+ "from data where name = '%s'";
+
+ if (!(ebn = escaped_basename(name)))
+ goto out;
+ q = make_message(verbose? verbose_fmt : fmt, ebn);
+ free(ebn);
+ result = get_result(q);
+ free(q);
+ if (!result)
+ goto out;
+ row = mysql_fetch_row(result);
+ if (!row || !row[0])
+ goto out;
+ ret = para_strdup(row[0]);
+out:
+ if (result)
+ mysql_free_result(result);
+ if (!ret && verbose)
+ ret = para_strdup("(not yet played)");
+ return ret;
+}
+
+static char *get_dir(char *name)
+{
+ char *ret = NULL, *q, *ebn;
+ void *result;
+ MYSQL_ROW row;
+
+ if (!(ebn = escaped_basename(name)))
+ return NULL;
+ q = make_message("select dir from dir where name = '%s'", ebn);
+ free(ebn);
+ result = get_result(q);
+ free(q);
+ if (!result)
+ return NULL;
+ row = mysql_fetch_row(result);
+ if (row && row[0])
+ ret = para_strdup(row[0]);
+ mysql_free_result(result);
+ return ret;
+}
+
+/* never returns NULL */
+static char *get_current_stream(void)
+{
+ char *ret;
+ MYSQL_ROW row;
+ void *result = get_result("select def from streams where "
+ "name = 'current_stream'");
+
+ if (!result)
+ goto err_out;
+ row = mysql_fetch_row(result);
+ if (!row || !row[0])
+ goto err_out;
+ ret = para_strdup(row[0]);
+ mysql_free_result(result);
+ return ret;
+err_out:
+ if (result)
+ mysql_free_result(result);
+ return para_strdup("(none)");
+}
+/*
+ * Read stream definition of stream streamname and construct mysql
+ * query. Return NULL on errors. If streamname is NULL, use current
+ * stream. If that is also NULL, use query that selects everything.
+ * If filename is NULL, query will list everything, otherwise only
+ * the score of given file.
+ */
+static char *get_query(char *streamname, char *filename, int with_path)
+{
+ char *accept_opts = NULL, *deny_opts = NULL, *score = NULL;
+ char *where_clause, *order, *query;
+ char command[255] = ""; /* buffer for sscanf */
+ void *result;
+ MYSQL_ROW row;
+ char *end, *tmp;
+ char *select_clause = NULL;
+ if (!streamname)
+ tmp = get_current_stream();
+ else
+ tmp = para_strdup(streamname);
+ if (!strcmp(tmp, "(none)")) {
+ free(tmp);
+ if (filename) {
+ char *ret, *ebn = escaped_basename(filename);
+ ret = make_message("select to_days(now()) - "
+ "to_days(lastplayed) from data "
+ "where name = '%s'", ebn);
+ free(ebn);
+ return ret;
+ }
+ if (with_path)
+ return make_message(
+ "select concat(dir.dir, '/', dir.name) "
+ "from data, dir where dir.name = data.name "
+ "order by data.lastplayed"
+ );
+ return make_message(
+ "select name from data where name is not NULL "
+ "order by lastplayed"
+ );
+ }
+ free(tmp);
+ query = make_message("select def from streams where name = '%s'",
+ streamname);
+ result = get_result(query);
+ free(query);
+ query = NULL;
+ if (!result)
+ goto out;
+ row = mysql_fetch_row(result);
+ if (!row || !row[0])
+ goto out;
+ end = row[0];
+ while (*end) {
+ int n;
+ char *arg, *line = end;
+
+ if (!(end = strchr(line, '\n')))
+ break;
+ *end = '\0';
+ end++;
+ if (sscanf(line, "%200s%n", command, &n) < 1)
+ continue;
+ arg = line + n;
+ if (!strcmp(command, "accept:")) {
+ char *tmp2 = s_a_r_list(macro_list, arg);
+ if (accept_opts)
+ accept_opts = para_strcat(
+ accept_opts, " or ");
+ accept_opts = para_strcat(accept_opts, tmp2);
+ free(tmp2);
+ continue;
+ }
+ if (!strcmp(command, "deny:")) {
+ char *tmp2 = s_a_r_list(macro_list, arg);
+ if (deny_opts)
+ deny_opts = para_strcat(deny_opts, " or ");
+ deny_opts = para_strcat(deny_opts, tmp2);
+ free(tmp2);
+ continue;
+ }
+ if (!strcmp(command, "score:"))
+ score = s_a_r_list(macro_list, arg);
+ }
+ if (!score) {
+ score = s_a_r_list(macro_list, conf.mysql_default_score_arg);
+ if (!score)
+ goto out;
+ }
+ if (filename) {
+ char *ebn = escaped_basename(filename);
+ if (!ebn)
+ goto out;
+ select_clause = make_message("select %s from data ", score);
+ free(score);
+ where_clause = make_message( "where name = '%s' ", ebn);
+ free(ebn);
+ order = para_strdup("");
+ goto write_query;
+ }
+ select_clause = para_strdup(with_path?
+ "select concat(dir.dir, '/', dir.name) from data, dir "
+ "where dir.name = data.name "
+ :
+ "select name from data where name is not NULL");
+ order = make_message("order by -(%s)", score);
+ free(score);
+ if (accept_opts && deny_opts) {
+ where_clause = make_message("and ((%s) and not (%s)) ",
+ accept_opts, deny_opts);
+ goto write_query;
+ }
+ if (accept_opts && !deny_opts) {
+ where_clause = make_message("and (%s) ", accept_opts);
+ goto write_query;
+ }
+ if (!accept_opts && deny_opts) {
+ where_clause = make_message("and not (%s) ", deny_opts);
+ goto write_query;
+ }
+ where_clause = para_strdup("");
+write_query:
+ query = make_message("%s %s %s", select_clause, where_clause, order);
+ free(order);
+ free(select_clause);
+ free(where_clause);
+out:
+ if (accept_opts)
+ free(accept_opts);
+ if (deny_opts)
+ free(deny_opts);
+ if (result)
+ mysql_free_result(result);
+ return query;
+}
+
+
+
+/*
+ * This is called from server and from some commands. Name must not be NULL
+ * Never returns NULL.
+ */
+static char *get_dbinfo(char *name)
+{
+ char *meta = NULL, *atts = NULL, *info, *dir = NULL, *query, *stream = NULL;
+ void *result = NULL;
+ MYSQL_ROW row = NULL;
+
+ if (!name)
+ return para_strdup("(none)");
+ stream = get_current_stream();
+ meta = get_meta(name, 1);
+ atts = get_atts(name, 0);
+ dir = get_dir(name);
+ /* get score */
+ query = get_query(stream, name, 0);
+ if (!query)
+ goto write;
+ result = get_result(query);
+ free(query);
+ if (result)
+ row = mysql_fetch_row(result);
+write:
+ info = make_message("dbinfo1:dir: %s\n"
+ "dbinfo2:stream: %s, %s, score: %s\n"
+ "dbinfo3:%s\n",
+ dir? dir : "(not contained in table)",
+ stream, meta,
+ (result && row && row[0])? row[0] : "(no score)",
+ atts);
+ if (dir)
+ free(dir);
+ if (meta)
+ free(meta);
+ if (atts)
+ free(atts);
+ if (stream)
+ free(stream);
+ if (result)
+ mysql_free_result(result);
+ return info;
+}
+
+
+/* might return NULL */
+static char *get_current_audio_file(void)
+{
+ char *name;
+ mmd_lock();
+ name = para_basename(mmd->filename);
+ mmd_unlock();
+ return name;
+}
+
+
+/* print database info */
+static int com_info(int fd, int argc, char *argv[])
+{
+ char *name = NULL, *meta = NULL, *atts = NULL, *dir = NULL;
+ int ret, com_la = strcmp(argv[0], "info");
+
+ if (argc < 1) {
+ ret = -E_GET_AUDIO_FILE;
+ if (!(name = get_current_audio_file()))
+ goto out;
+ ret = send_va_buffer(fd, "%s\n", name);
+ if (ret < 0)
+ goto out;
+ } else {
+ ret = -E_ESCAPE;
+ if (!(name = escaped_basename(argv[1])))
+ goto out;
+ }
+ meta = get_meta(name, 1);
+ atts = get_atts(name, 0);
+ dir = get_dir(name);
+ if (com_la)
+ ret = send_va_buffer(fd, "%s\n", atts);
+ else
+ ret = send_va_buffer(fd, "dir: %s\n" "%s\n" "attributes: %s\n",
+ dir? dir : "(not contained in table)", meta, atts);
+out:
+ if (meta)
+ free(meta);
+ if (atts)
+ free(atts);
+ if (dir)
+ free(dir);
+ if (name)
+ free(name);
+ return ret;
+}
+static int change_stream(char *stream)
+{
+ char *query;
+ int ret;
+ /* try to insert if it does not exist (compatibility) */
+// query = make_message("insert into streams (name, def) values "
+// "('current_stream', '%s')", stream);
+// real_query(query); /* ignore return value */
+// free(query);
+ query = make_message("update streams set def='%s' "
+ "where name = 'current_stream'", stream);
+ ret = real_query(query);
+ free(query);
+ return ret;
+}
+
+static int get_pic_id_by_name(char *name)
+{
+ char *q, *ebn;
+ void *result = NULL;
+ long unsigned ret;
+ MYSQL_ROW row;
+
+ if (!(ebn = escaped_basename(name)))
+ return -E_ESCAPE;
+ q = make_message("select pic_id from data where name = '%s'", ebn);
+ free(ebn);
+ result = get_result(q);
+ free(q);
+ if (!result)
+ return -E_NORESULT;
+ row = mysql_fetch_row(result);
+ ret = -E_NOROW;
+ if (row && row[0])
+ ret = atol(row[0]);
+ mysql_free_result(result);
+ return ret;
+}
+
+static int remove_entry(const char *name)
+{
+ char *q, *ebn = escaped_basename(name);
+ int ret = -E_ESCAPE;
+
+ if (!ebn || !*ebn)
+ goto out;
+ q = make_message("delete from data where name = '%s'", ebn);
+ real_query(q); /* ignore errors */
+ free(q);
+ q = make_message("delete from dir where name = '%s'", ebn);
+ real_query(q); /* ignore errors */
+ free(q);
+ ret = 1;
+out:
+ free(ebn);
+ return ret;
+}
+
+static int add_entry(const char *name)
+{
+ char *q, *dn, *ebn = NULL, *edn = NULL;
+ int ret;
+
+ if (!name || !*name)
+ return -E_MYSQL_SYNTAX;
+ ebn = escaped_basename(name);
+ if (!ebn)
+ return -E_ESCAPE;
+ ret = -E_MYSQL_SYNTAX;
+ dn = para_dirname(name);
+ if (!dn)
+ goto out;
+ ret = -E_ESCAPE;
+ edn = escape_str(dn);
+ free(dn);
+ if (!edn || !*edn)
+ goto out;
+ q = make_message("insert into data (name, pic_id) values "
+ "('%s', '%s')", ebn, "1");
+ ret = real_query(q);
+// ret = 1; PARA_DEBUG_LOG("q: %s\n", q);
+ free(q);
+ if (ret < 0)
+ goto out;
+ q = make_message("insert into dir (name, dir) values "
+ "('%s', '%s')", ebn, edn);
+// ret = 1; PARA_DEBUG_LOG("q: %s\n", q);
+ ret = real_query(q);
+ free(q);
+out:
+ if (ebn)
+ free(ebn);
+ if (edn)
+ free(edn);
+ return ret;
+}
+
+/*
+ * remove/add entries
+ */
+static int com_rm_ne(__unused int fd, int argc, char *argv[])
+{
+ int ne = !strcmp(argv[0], "ne");
+ int i, ret;
+ if (argc < 1)
+ return -E_MYSQL_SYNTAX;
+ for (i = 1; i <= argc; i++) {
+ ret = remove_entry(argv[i]);
+ if (ret < 0)
+ return ret;
+ if (!ne)
+ continue;
+ ret = add_entry(argv[i]);
+ if (ret < 0)
+ return ret;
+ }
+ return 1;
+}
+
+/*
+ * mv: rename entry
+ */
+static int com_mv(__unused int fd, int argc, char *argv[])
+{
+ char *q, *dn, *ebn1 = NULL, *ebn2 = NULL, *edn = NULL;
+ int ret;
+
+ if (argc != 2)
+ return -E_MYSQL_SYNTAX;
+ ebn1 = escaped_basename(argv[1]);
+ ebn2 = escaped_basename(argv[2]);
+ dn = para_dirname(argv[2]);
+ edn = escape_str(dn);
+ free(dn);
+ ret = -E_ESCAPE;
+ if (!ebn1 || !ebn2)
+ goto out;
+ remove_entry(ebn2);
+ q = make_message("update data set name = '%s' where name = '%s'",
+ ebn2, ebn1);
+ ret = real_query(q);
+ free(q);
+ if (ret < 0)
+ goto out;
+ q = make_message("update dir set name = '%s' where name = '%s'",
+ ebn2, ebn1);
+ ret = real_query(q);
+ free(q);
+ if (ret < 0)
+ goto out;
+ /* do not touch table dir, return success if argv[2] is no full path */
+ ret = 1;
+ if (!edn || !*edn)
+ goto out;
+ q = make_message("update dir set dir = '%s' where name = '%s'",
+ edn, ebn2);
+// PARA_DEBUG_LOG("q: %s\n", q);
+ ret = real_query(q);
+ free(q);
+out:
+ if (ebn1)
+ free(ebn1);
+ if (ebn2)
+ free(ebn2);
+ if (edn)
+ free(edn);
+ return ret;
+
+}
+
+/*
+ * picass: associate pic to audio file
+ * snp: set numplayed
+ */
+static int com_set(__unused int fd, int argc, char *argv[])
+{
+ char *q, *ebn;
+ long unsigned id;
+ int i, ret;
+ char *field = strcmp(argv[0], "picass")? "numplayed" : "pic_id";
+
+ if (argc < 2)
+ return -E_MYSQL_SYNTAX;
+ id = atol(argv[1]);
+ for (i = 2; i <= argc; i++) {
+ ebn = escaped_basename(argv[i]);
+ if (!ebn)
+ return -E_ESCAPE;
+ q = make_message("update data set %s = %lu "
+ "where name = '%s'", field, id, ebn);
+ free(ebn);
+ ret = real_query(q);
+ free(q);
+ if (ret < 0)
+ return ret;
+ }
+ return 1;
+}
+
+/*
+ * picch: change entry's name in pics table
+ */
+static int com_picch(__unused int fd, int argc, char *argv[])
+{
+ int ret;
+ long unsigned id;
+ char *q;
+
+ if (argc != 2)
+ return -E_MYSQL_SYNTAX;
+ id = atol(argv[1]);
+ if (strlen(argv[2]) > MAXLINE)
+ return -E_NAMETOOLONG;
+ q = make_message("update pics set name = '%s' where id = %lu", argv[2], id);
+ ret = real_query(q);
+ free(q);
+ return ret;
+}
+
+/*
+ * piclist: print list of pics in db
+ */
+static int com_piclist(__unused int fd, int argc, __unused char *argv[])
+{
+ void *result = NULL;
+ MYSQL_ROW row;
+ unsigned long *length;
+ int ret;
+
+ if (argc)
+ return -E_MYSQL_SYNTAX;
+ result = get_result("select id,name,pic from pics order by id");
+ if (!result)
+ return -E_NORESULT;
+ while ((row = mysql_fetch_row(result))) {
+ length = mysql_fetch_lengths(result);
+ if (!row || !row[0] || !row[1] || !row[2])
+ continue;
+ ret = send_va_buffer(fd, "%s\t%lu\t%s\n", row[0], length[2], row[1]);
+ if (ret < 0)
+ goto out;
+ }
+ ret = 1;
+out:
+ mysql_free_result(result);
+ return ret;
+}
+
+/*
+ * picdel: delete picture from database
+ */
+static int com_picdel(int fd, int argc, char *argv[])
+{
+ char *q;
+ long unsigned id;
+ my_ulonglong aff;
+ int i, ret;
+
+ if (argc < 1)
+ return -E_MYSQL_SYNTAX;
+ for (i = 1; i <= argc; i++) {
+ id = atol(argv[i]);
+ q = make_message("delete from pics where id = %lu", id);
+ ret = real_query(q);
+ free(q);
+ if (ret < 0)
+ return ret;
+ aff = mysql_affected_rows(mysql_ptr);
+ if (!aff) {
+ ret = send_va_buffer(fd, "No such id: %lu\n", id);
+ if (ret < 0)
+ return ret;
+ continue;
+ }
+ q = make_message("update data set pic_id = 1 where pic_id = %lu", id);
+ ret = real_query(q);
+ free(q);
+ }
+ return 1;
+}
+/*
+ * pic: get picture by name or by number
+ */
+static int com_pic(int fd, int argc, char *argv[])
+{
+ void *result = NULL;
+ MYSQL_ROW row;
+ unsigned long *length, id;
+ int ret;
+ char *q, *name = NULL;
+
+ if (argc < 1) {
+ ret = -E_GET_AUDIO_FILE;
+ name = get_current_audio_file();
+ } else {
+ ret = -E_ESCAPE;
+ name = escaped_basename(argv[1]);
+ }
+ if (!name)
+ return ret;
+ if (*name == '#')
+ id = atoi(name + 1);
+ else
+ id = get_pic_id_by_name(name);
+ free(name);
+ if (id <= 0)
+ return id;
+ q = make_message("select pic from pics where id = '%lu'", id);
+ result = get_result(q);
+ free(q);
+ if (!result)
+ return -E_NORESULT;
+ row = mysql_fetch_row(result);
+ ret = -E_NOROW;
+ if (!row || !row[0])
+ goto out;
+ length = mysql_fetch_lengths(result);
+ ret = send_bin_buffer(fd, row[0], *length);
+out:
+ mysql_free_result(result);
+ return ret;
+}
+
+/* strdel */
+static int com_strdel(__unused int fd, int argc, char *argv[])
+{
+ char *tmp;
+ int ret = -1;
+
+ if (argc < 1)
+ return -E_MYSQL_SYNTAX;
+ tmp = make_message("delete from streams where name='%s'", argv[1]);
+ ret = real_query(tmp);
+ free(tmp);
+ if (ret < 0)
+ return ret;
+ tmp = get_current_stream();
+ ret = 1;
+ if (strcmp(tmp, "(none)") && !strcmp(tmp, argv[1]))
+ ret = change_stream("(none)");
+ return ret;
+}
+
+/*
+ * ls
+ */
+static int com_ls(int fd, int argc, char *argv[])
+{
+ char *q;
+ void *result;
+ int ret;
+ unsigned int num_rows;
+
+ if (argc > 0)
+ q = make_message("select name from data where name LIKE '%s'",
+ argv[1]);
+ else
+ q = para_strdup("select name from data");
+ result = get_result(q);
+ free(q);
+ if (!result)
+ return -E_NORESULT;
+ num_rows = mysql_num_rows(result);
+ ret = 1;
+ if (num_rows)
+ ret = print_results(fd, result, 0, 0, num_rows - 1, 0);
+ mysql_free_result(result);
+ return ret;
+}
+/*
+ * summary
+ */
+static int com_summary(__unused int fd, int argc, __unused char *argv[])
+{
+ MYSQL_ROW row;
+ MYSQL_ROW row2;
+ void *result;
+ void *result2 = NULL;
+ const char *fmt = "select count(name) from data where %s='1'";
+ int ret = -E_NORESULT;
+
+ if (argc)
+ return -E_MYSQL_SYNTAX;
+ result = get_all_attributes();
+ if (!result)
+ goto out;
+ while ((row = mysql_fetch_row(result))) {
+ char *buf;
+
+ ret = -E_NOROW;
+ if (!row[0])
+ goto out;
+ ret = -E_NORESULT;
+ buf = make_message(fmt, row[0]);
+ result2 = get_result(buf);
+ free(buf);
+ if (!result2)
+ goto out;
+ ret = -E_NOROW;
+ row2 = mysql_fetch_row(result2);
+ if (!row2 || !row2[0])
+ goto out;
+ ret = send_va_buffer(fd, "%s\t%s\n", row[0], row2[0]);
+ if (ret < 0)
+ goto out;
+ }
+ ret = 1;
+out:
+ if (result2)
+ mysql_free_result(result2);
+ if (result)
+ mysql_free_result(result);
+ return ret;
+}
+
+static int get_numplayed(char *name)
+{
+ void *result;
+ MYSQL_ROW row;
+ const char *fmt = "select numplayed from data where name = '%s'";
+ char *buf = make_message(fmt, name);
+ int ret = -E_NORESULT;
+
+ result = get_result(buf);
+ free(buf);
+ if (!result)
+ goto out;
+ ret = -E_NOROW;
+ row = mysql_fetch_row(result);
+ if (!row || !row[0])
+ goto out;
+ ret = atoi(row[0]);
+out:
+ if (result)
+ mysql_free_result(result);
+ return ret;
+}
+
+static int update_audio_file(char *name)
+{
+ int ret;
+ const char *fmt1 = "update data set lastplayed = now() where name = '%s'";
+ const char *fmt2 = "update data set numplayed = %i where name = '%s'";
+ char *q;
+ char *ebn = escaped_basename(name);
+
+ ret = -E_ESCAPE;
+ if (!ebn)
+ goto out;
+ q = make_message(fmt1, ebn);
+ ret = real_query(q);
+ free(q);
+ if (ret < 0)
+ goto out;
+ ret = get_numplayed(ebn);
+ if (ret < 0)
+ goto out;
+ q = make_message(fmt2, ret + 1, ebn);
+ ret = real_query(q);
+ free(q);
+out:
+ if (ebn)
+ free(ebn);
+ return ret;
+}
+/* If called as child, mmd_lock must be held */
+static void update_mmd(char *info)
+{
+ PARA_DEBUG_LOG("%s", "updating shared memory area\n");
+ strncpy(mmd->dbinfo, info, MMD_INFO_SIZE - 1);
+ mmd->dbinfo[MMD_INFO_SIZE - 1] = '\0';
+}
+
+static void update_audio_file_server_handler(char *name)
+{
+ char *info;
+ info = get_dbinfo(name);
+ update_mmd(info);
+ free(info);
+ update_audio_file(name);
+}
+
+static int com_us(__unused int fd, int argc, char *argv[])
+{
+ if (argc != 1)
+ return -E_MYSQL_SYNTAX;
+ return update_audio_file(argv[1]);
+}
+
+static void refresh_mmd_dbinfo(void)
+{
+ char *name = get_current_audio_file();
+ char *info;
+
+ if (!name)
+ return;
+ info = get_dbinfo(name);
+ free(name);
+ mmd_lock();
+ update_mmd(info);
+ mmd_unlock();
+ free(info);
+}
+
+/* select previous/next stream */
+static int com_ps(__unused int fd, int argc, char *argv[])
+{
+ char *query, *stream = get_current_stream();
+ void *result = get_result("select name from streams");
+ MYSQL_ROW row;
+ int match = -1, ret, i;
+ unsigned int num_rows;
+
+ if (argc)
+ return -E_MYSQL_SYNTAX;
+ ret = -E_NORESULT;
+ if (!result)
+ goto out;
+ num_rows = mysql_num_rows(result);
+ ret = -E_EMPTY_RESULT;
+ if (num_rows < 2)
+ goto out;
+ ret = -E_NOROW;
+ for (i = 0; i < num_rows; i++) {
+ row = mysql_fetch_row(result);
+ if (!row || !row[0])
+ goto out;
+ if (!strcmp(row[0], "current_stream"))
+ continue;
+ if (!strcmp(row[0], stream)) {
+ match = i;
+ break;
+ }
+ }
+ ret = -E_NO_STREAM;
+ if (match < 0)
+ goto out;
+ if (!strcmp(argv[0], "ps"))
+ i = match > 0? match - 1 : num_rows - 1;
+ else
+ i = match < num_rows - 1? match + 1 : 0;
+ ret = -E_NOROW;
+ mysql_data_seek(result, i);
+ row = mysql_fetch_row(result);
+ if (!row || !row[0])
+ goto out;
+ if (!strcmp(row[0], "current_stream")) {
+ if (!strcmp(argv[0], "ps")) {
+ i = match - 2;
+ i = i < 0? i + num_rows : i;
+ } else {
+ i = match + 2;
+ i = i > num_rows - 1? i - num_rows : i;
+ }
+ mysql_data_seek(result, i);
+ row = mysql_fetch_row(result);
+ if (!row || !row[0])
+ goto out;
+ }
+ query = make_message("update streams set def='%s' where name = "
+ "'current_stream'", row[0]);
+ ret = real_query(query);
+ free(query);
+ refresh_mmd_dbinfo();
+out:
+ free(stream);
+ if (result)
+ mysql_free_result(result);
+ return ret;
+}
+
+/* streams */
+static int com_streams(int fd, int argc, __unused char *argv[])
+{
+ unsigned int num_rows;
+ int i, ret = -E_NORESULT;
+ void *result;
+ MYSQL_ROW row;
+
+ if (argc && strcmp(argv[1], "current_stream"))
+ return -E_MYSQL_SYNTAX;
+ if (argc) {
+ char *cs = get_current_stream();
+ ret = send_va_buffer(fd, "%s\n", cs);
+ free(cs);
+ return ret;
+ }
+ result = get_result("select name from streams");
+ if (!result)
+ goto out;
+ num_rows = mysql_num_rows(result);
+ ret = 1;
+ if (!num_rows)
+ goto out;
+ ret = -E_NOROW;
+ for (i = 0; i < num_rows; i++) {
+ row = mysql_fetch_row(result);
+ if (!row || !row[0])
+ goto out;
+ if (strcmp(row[0], "current_stream"))
+ send_va_buffer(fd, "%s\n", row[0]);
+ }
+ ret = 1;
+out:
+ if (result)
+ mysql_free_result(result);
+ return ret;
+}
+
+/* query stream definition */
+static int com_strq(int fd, int argc, char *argv[])
+{
+ MYSQL_ROW row;
+ char *query, *name;
+ void *result;
+ int ret;
+
+ if (argc < 1) {
+ ret = -E_GET_STREAM;
+ name = get_current_stream();
+ } else {
+ ret = -E_ESCAPE;
+ name = escaped_basename(argv[1]);
+ }
+ if (!name)
+ return ret;
+ ret = -E_NORESULT;
+ query = make_message("select def from streams where name='%s'", name);
+ free(name);
+ result = get_result(query);
+ free(query);
+ if (!result)
+ goto out;
+ ret = -E_NOROW;
+ row = mysql_fetch_row(result);
+ if (!row || !row[0])
+ goto out;
+ /* no '\n' needed */
+ ret = send_buffer(fd, row[0]);
+out:
+ if (result)
+ mysql_free_result(result);
+ return ret;
+}
+
+/* change stream / change stream and play */
+static int com_cs(int fd, int argc, char *argv[])
+{
+ int ret, stream_change;
+ char *query;
+ char *old_stream = get_current_stream();
+ int csp = !strcmp(argv[0], "csp");
+
+ if (!argc) {
+ ret = -E_MYSQL_SYNTAX;
+ if (csp)
+ goto out;
+ ret = send_va_buffer(fd, "%s\n", old_stream);
+ goto out;
+ }
+ ret = -E_GET_QUERY;
+ query = get_query(argv[1], NULL, 0); /* test if stream is valid */
+ if (!query)
+ goto out;
+ free(query);
+ /* stream is ok */
+ stream_change = strcmp(argv[1], old_stream);
+ if (stream_change) {
+ ret = change_stream(argv[1]);
+ if (ret < 0)
+ goto out;
+ refresh_mmd_dbinfo();
+ }
+ if (csp) {
+ mmd_lock();
+ mmd->new_afs_status_flags |= AFS_PLAYING;
+ if (stream_change)
+ mmd->new_afs_status_flags |= AFS_NEXT;
+ mmd_unlock();
+ }
+ ret = 1;
+out:
+ free(old_stream);
+ return ret;
+}
+
+/*
+ * sl/skip
+ */
+static int com_sl(int fd, int argc, char *argv[])
+{
+ void *result = NULL;
+ MYSQL_ROW row;
+ int ret, i, skip = !strcmp(argv[0], "skip");
+ char *query, *stream, *tmp;
+ unsigned int num_rows, num;
+
+ if (argc < 1)
+ return -E_MYSQL_SYNTAX;
+ num = atoi(argv[1]);
+ if (!num)
+ return -E_MYSQL_SYNTAX;
+ stream = (argc == 1)? get_current_stream() : para_strdup(argv[2]);
+ tmp = get_query(stream, NULL, 0);
+ query = make_message("%s limit %d", tmp, num);
+ free(tmp);
+ ret = -E_GET_QUERY;
+ free(stream);
+ if (!query)
+ goto out;
+ ret = -E_NORESULT;
+ result = get_result(query);
+ free(query);
+ if (!result)
+ goto out;
+ ret = -E_EMPTY_RESULT;
+ num_rows = mysql_num_rows(result);
+ if (!num_rows)
+ goto out;
+ for (i = 0; i < num_rows && i < num; i++) {
+ row = mysql_fetch_row(result);
+ if (skip) {
+ send_va_buffer(fd, "Skipping %s\n", row[0]);
+ update_audio_file(row[0]);
+ } else
+ send_va_buffer(fd, "%s\n", row[0]? row[0]: "BUG");
+ }
+ ret = 1;
+out:
+ if (result)
+ mysql_free_result(result);
+ return ret;
+}
+
+/*
+ * update attributes of name
+ */
+static int update_atts(int fd, char *name, char *atts)
+{
+ int ret;
+ char *ebn, *q, *old, *new = NULL;
+
+ if (!mysql_ptr)
+ return -E_NOTCONN;
+ ebn = escaped_basename(name);
+ if (!ebn)
+ return -E_ESCAPE;
+ q = make_message("update data set %s where name = '%s'", atts, ebn);
+ old = get_atts(ebn, 0);
+ send_va_buffer(fd, "old: %s\n", old);
+ free(old);
+ ret = real_query(q);
+ free(q);
+ if (ret < 0)
+ goto out;
+ new = get_atts(ebn, 0);
+ ret = send_va_buffer(fd, "new: %s\n", new);
+ free(new);
+out:
+ free(ebn);
+ return ret;
+}
+
+/*
+ * set attributes
+ */
+static int com_sa(int fd, int argc, char *argv[])
+{
+ int i, ret;
+ char *atts = NULL, *name;
+
+ if (argc < 1)
+ return -E_MYSQL_SYNTAX;
+ for (i = 1; i <= argc; i++) {
+ int unset = 0;
+ char *tmp, *p =argv[i];
+ int len = strlen(p);
+
+ if (!len)
+ continue;
+ switch (p[len - 1]) {
+ case '+':
+ unset = 0;
+ break;
+ case '-':
+ unset = 1;
+ break;
+ default:
+ goto no_more_atts;
+ }
+ p[len - 1] = '\0';
+ tmp = make_message("%s%s='%s'", atts? "," : "", p,
+ unset? "0" : "1");
+ atts = para_strcat(atts, tmp);
+ free(tmp);
+ }
+no_more_atts:
+ if (!atts)
+ return -E_NOATTS;
+ if (i > argc) { /* no name given, use current af */
+ ret = -E_GET_AUDIO_FILE;
+ if (!(name = get_current_audio_file()))
+ goto out;
+ ret = update_atts(fd, name, atts);
+ free(name);
+ } else {
+ ret = 1;
+ for (; argv[i] && ret >= 0; i++)
+ ret = update_atts(fd, argv[i], atts);
+ }
+ refresh_mmd_dbinfo();
+out:
+ return ret;
+}
+
+/*
+ * copy attributes
+ */
+static int com_cam(int fd, int argc, char *argv[])
+{
+ char *name = NULL, *meta = NULL, *atts = NULL;
+ int i, ret;
+
+ if (argc < 2)
+ return -E_MYSQL_SYNTAX;
+ if (!(name = escaped_basename(argv[1])))
+ return -E_ESCAPE;
+ ret = -E_NOATTS;
+ if (!(atts = get_atts(name, 1)))
+ goto out;
+ ret = -E_META;
+ if (!(meta = get_meta(name, 0)))
+ goto out;
+ for (i = 2; i <= argc; i++) {
+ char *ebn, *q;
+ ret = -E_ESCAPE;
+ if (!(ebn = escaped_basename(argv[i])))
+ goto out;
+ ret = send_va_buffer(fd, "updating %s\n", ebn);
+ if (ret < 0) {
+ free(ebn);
+ goto out;
+ }
+ q = make_message("update data set %s where name = '%s'",
+ meta, ebn);
+ if ((ret = update_atts(fd, ebn, atts)) >= 0)
+ ret = real_query(q);
+ free(ebn);
+ free(q);
+ if (ret < 0)
+ goto out;
+ }
+ ret = 1;
+out:
+ if (name)
+ free(name);
+ if (meta)
+ free(meta);
+ if (atts)
+ free(atts);
+ return ret;
+}
+
+/*
+ * verify / clean
+ */
+static int com_vrfy(int fd, int argc, __unused char *argv[])
+{
+ char *query;
+ int ret, vrfy_mode = strcmp(argv[0], "clean");
+ void *result = NULL;
+ unsigned int num_rows;
+ MYSQL_ROW row;
+ char *escaped_name;
+
+ if (argc)
+ return -E_MYSQL_SYNTAX;
+ ret = -E_NORESULT;
+ result = get_result("select data.name from data left join dir on "
+ "dir.name = data.name where dir.name is NULL");
+ if (!result)
+ goto out;
+ num_rows = mysql_num_rows(result);
+ if (!num_rows) {
+ ret = send_buffer(fd, "No invalid entries\n");
+ goto out;
+ }
+ if (vrfy_mode) {
+ send_va_buffer(fd, "found %i invalid entr%s\n", num_rows,
+ num_rows == 1? "y" : "ies");
+ ret = print_results(fd, result, 0, 0, num_rows - 1, 0);
+ goto out;
+ }
+ while ((row = mysql_fetch_row(result))) {
+ ret = -E_NOROW;
+ if (!row[0])
+ goto out;
+ ret = -E_ESCAPE;
+ escaped_name = escape_str(row[0]);
+ if (!escaped_name)
+ goto out;
+ send_va_buffer(fd, "deleting %s\n", escaped_name);
+ query = make_message("delete from data where name = '%s'",
+ escaped_name);
+ ret = real_query(query);
+ free(query);
+ if (ret < 0)
+ goto out;
+ }
+
+out:
+ if (result)
+ mysql_free_result(result);
+ return ret;
+}
+
+static FILE *out_file;
+
+static int mysql_write_tmp_file(const char *dir, const char *name)
+{
+ int ret = -E_TMPFILE;
+ char *msg = make_message("%s\t%s\n", dir, name);
+
+ if (fputs(msg, out_file) != EOF)
+ ret = 1;
+ free(msg);
+ return ret;
+}
+
+/*
+ * update database
+ */
+static int com_upd(int fd, int argc, __unused char *argv[])
+{
+ char *tempname = NULL, *query = NULL;
+ int ret, out_fd = -1, num = 0;
+ void *result = NULL;
+ unsigned int num_rows;
+ MYSQL_ROW row;
+
+ if (argc)
+ return -E_MYSQL_SYNTAX;
+ out_file = NULL;
+ tempname = para_strdup("/tmp/mysql.tmp.XXXXXX");
+ ret = para_mkstemp(tempname, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ if (ret < 0)
+ goto out;
+ out_fd = ret;
+ out_file = fdopen(out_fd, "w");
+ if (!out_file) {
+ close(out_fd);
+ goto out;
+ }
+ if (find_audio_files(conf.mysql_audio_file_dir_arg, mysql_write_tmp_file) < 0)
+ goto out;
+ num = ftell(out_file);
+ /*
+ * we have to make sure the file hit the disk before we call
+ * real_query
+ */
+ fclose(out_file);
+ out_file = NULL;
+ PARA_DEBUG_LOG("wrote tempfile %s (%d bytes)\n", tempname, num);
+ if (!num)
+ goto out;
+ if ((ret = real_query("delete from dir")) < 0)
+ goto out;
+ query = make_message("load data infile '%s' into table dir "
+ "fields terminated by '\t' lines terminated by '\n' "
+ "(dir, name)", tempname);
+ ret = real_query(query);
+ free(query);
+ if (ret < 0)
+ goto out;
+ result = get_result("select dir.name from dir left join data on "
+ "data.name = dir.name where data.name is NULL");
+ ret = -E_NORESULT;
+ if (!result)
+ goto out;
+ num_rows = mysql_num_rows(result);
+ if (!num_rows) {
+ ret = send_buffer(fd, "no new entries\n");
+ goto out;
+ }
+ while ((row = mysql_fetch_row(result))) {
+ ret = -E_NOROW;
+ if (!row[0])
+ goto out;
+ send_va_buffer(fd, "new entry: %s\n", row[0]);
+ query = make_message("insert into data (name, pic_id) values "
+ "('%s','%s')", row[0], "1");
+ ret = real_query(query);
+ free(query);
+ if (ret < 0)
+ goto out;
+ }
+ ret = 1;
+out:
+ if (out_fd >= 0)
+ unlink(tempname);
+ free(tempname);
+ if (out_file)
+ fclose(out_file);
+ if (result)
+ mysql_free_result(result);
+ return ret;
+}
+
+static char **server_get_audio_file_list(unsigned int num)
+{
+ char **list = para_malloc((num + 1) * sizeof(char *));
+ char *tmp, *query, *stream = get_current_stream();
+ void *result = NULL;
+ unsigned int num_rows;
+ int i = 0;
+ MYSQL_ROW row;
+
+ tmp = get_query(stream, NULL, 1);
+ free(stream);
+ query = make_message("%s limit %d", tmp, num);
+ free(tmp);
+ if (!query)
+ goto err_out;
+ result = get_result(query);
+ if (!result)
+ goto err_out;
+ num_rows = mysql_num_rows(result);
+ if (!num_rows)
+ goto err_out;
+ for (i = 0; i < num_rows && i < num; i++) {
+ row = mysql_fetch_row(result);
+ if (!row || !row[0])
+ goto err_out;
+ list[i] = para_strdup(row[0]);
+ }
+ list[i] = NULL;
+ goto success;
+err_out:
+ while (i > 0) {
+ i--;
+ free(list[i]);
+ }
+ free(list);
+ list = NULL;
+success:
+ if (query)
+ free(query);
+ if (result)
+ mysql_free_result(result);
+ return list;
+}
+
+/*
+ * connect to mysql server, return mysql pointer on success, -E_NOTCONN
+ * on errors. Called from parent on startup and also from com_cdb().
+ */
+static int init_mysql_server(void)
+{
+ char *u = conf.mysql_user_arg? conf.mysql_user_arg : para_logname();
+
+ mysql_ptr = mysql_init(NULL);
+ if (!mysql_ptr) {
+ PARA_CRIT_LOG("%s", "mysql init error\n");
+ return -E_NOTCONN;
+ }
+ PARA_DEBUG_LOG("connecting: %s@%s:%d\n", u, conf.mysql_host_arg,
+ conf.mysql_port_arg);
+ if (!conf.mysql_user_arg)
+ free(u);
+ /*
+ * If host is NULL a connection to the local host is assumed,
+ * If user is NULL, the current user is assumed
+ */
+ if (!(mysql_ptr = mysql_real_connect(mysql_ptr,
+ conf.mysql_host_arg,
+ conf.mysql_user_arg,
+ conf.mysql_passwd_arg,
+ conf.mysql_database_arg,
+ conf.mysql_port_arg, NULL, 0))) {
+ PARA_CRIT_LOG("%s", "connect error\n");
+ return -E_NOTCONN;
+ }
+ PARA_INFO_LOG("%s", "success\n");
+ return 1;
+}
+
+/* mmd lock must be held */
+static void write_msg2mmd(int success)
+{
+ sprintf(mmd->dbinfo, "dbinfo1:%s\ndbinfo2:mysql-%s\ndbinfo3:\n",
+ success < 0? PARA_STRERROR(-success) :
+ "successfully connected to mysql server",
+ success < 0? "" : mysql_get_server_info(mysql_ptr));
+}
+
+/* create database */
+static int com_cdb(int fd, int argc, char *argv[])
+{
+ char *query, *name;
+ int ret;
+
+ if (argc < 1)
+ name = "paraslash";
+ else {
+ ret = -E_NAMETOOLONG;
+ name = argv[1];
+ if (strlen(name) > MAXLINE)
+ goto out;
+ }
+ if (mysql_ptr) {
+ PARA_INFO_LOG("%s", "closing database\n");
+ mysql_close(mysql_ptr);
+ }
+ /* dont use any database */
+ conf.mysql_database_arg = NULL; /* leak? */
+ ret = -E_MYSQL_INIT;
+ if (init_mysql_server() < 0 || !mysql_ptr)
+ goto out;
+ query = make_message("create database %s", name);
+ ret = real_query(query);
+ free(query);
+ if (ret < 0)
+ goto out;
+ /* reconnect with database just created */
+ mysql_close(mysql_ptr);
+ conf.mysql_database_arg = para_strdup(name);
+ ret = -E_MYSQL_INIT;
+ if (init_mysql_server() < 0 || !mysql_ptr)
+ goto out;
+ mmd_lock();
+ write_msg2mmd(1);
+ mmd_unlock();
+ ret = -E_QFAILED;
+ if (real_query("create table data (name varchar(255) binary not null "
+ "primary key, "
+ "lastplayed datetime not null default "
+ "'1970-01-01', "
+ "numplayed int not null default 0, "
+ "pic_id bigint unsigned not null default 1)") < 0)
+ goto out;
+ if (real_query("create table dir (name varchar(255) binary not null "
+ "primary key, dir varchar(255) default null)") < 0)
+ goto out;
+ if (real_query("create table pics ("
+ "id bigint(20) unsigned not null primary key "
+ "auto_increment, "
+ "name varchar(255) binary not null, "
+ "pic mediumblob not null)") < 0)
+ goto out;
+ if (real_query("create table streams ("
+ "name varchar(255) binary not null primary key, "
+ "def blob not null)") < 0)
+ goto out;
+ if (real_query("insert into streams (name, def) values "
+ "('current_stream', '(none)')") < 0)
+ goto out;
+ ret = send_va_buffer(fd, "successfully created database %s\n", name);
+out:
+ return ret;
+}
+
+static void shutdown_connection(void)
+{
+ if (mysql_ptr) {
+ PARA_NOTICE_LOG("%s", "shutting down mysql connection\n");
+ mysql_close(mysql_ptr);
+ mysql_ptr = NULL;
+ }
+}
+
+/**
+ * the init function of the mysql-based database tool
+ *
+ * Check the command line options and initialize all function pointers of \a db.
+ * Connect to the mysql server and initialize the dbinfo string.
+ *
+ * \sa struct dbtool, misc_meta_data::dbinfo, dopey.c
+ */
+int mysql_dbtool_init(struct dbtool *db)
+{
+ int ret;
+
+ if (!conf.mysql_passwd_given)
+ return -E_NO_MYSQL_PASSWD;
+ if (!conf.mysql_audio_file_dir_given)
+ return -E_NO_AF_DIR;
+ db->name = "mysql";
+ db->cmd_list = cmds;
+ db->get_audio_file_list = server_get_audio_file_list;
+ db->update_audio_file = update_audio_file_server_handler;
+ db->shutdown = shutdown_connection;
+ ret = init_mysql_server();
+ if (ret < 0)
+ PARA_WARNING_LOG("%s\n", PARA_STRERROR(-ret));
+ write_msg2mmd(ret);
+ return 1; /* return success even if connect failed to give the
+ * user the chance to exec com_cdb
+ */
+}
--- /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 net.c networking-related helper functions */
+
+#include "para.h"
+#include "net.h"
+#include "gcc-compat.h"
+#include <netdb.h>
+#include "error.h"
+#include "string.h"
+
+extern void (*crypt_function_recv)(unsigned long len, const unsigned char *indata, unsigned char *outdata);
+extern void (*crypt_function_send)(unsigned long len, const unsigned char *indata, unsigned char *outdata);
+
+/**
+ * initialize a struct sockaddr_in
+ * @param addr A pointer to the struct to be initialized
+ * @param port The port number to use
+ * @param he The address to use
+ *
+ * If \a he is null (server mode), \a addr->sin_addr is initialized with \p INADDR_ANY.
+ * Otherwise, the address given by \a he is copied to addr.
+ */
+void init_sockaddr(struct sockaddr_in *addr, int port, const struct hostent *he)
+{
+ /* host byte order */
+ addr->sin_family = AF_INET;
+ /* short, network byte order */
+ addr->sin_port = htons(port);
+ if (he)
+ addr->sin_addr = *((struct in_addr *)he->h_addr);
+ else
+ addr->sin_addr.s_addr = INADDR_ANY;
+ /* zero the rest of the struct */
+ memset(&addr->sin_zero, '\0', 8);
+}
+
+/*
+ * send out a buffer, resend on short writes
+ * @param fd the file descriptor
+ * @param buf The buffer to be sent
+ * @len The length of \a buf
+ *
+ * Due to circumstances beyond your control, the kernel might not send all the
+ * data out in one chunk, and now, my friend, it's up to us to get the data out
+ * there (Beej's Guide to Network Programming).
+ */
+static int sendall(int fd, const char *buf, size_t *len)
+{
+ int total = 0; /* how many bytes we've sent */
+ int bytesleft = *len; /* how many we have left to send */
+ int n = -1;
+
+ while (total < *len) {
+ n = send(fd, buf + total, bytesleft, 0);
+ if (n == -1)
+ break;
+ total += n;
+ bytesleft -= n;
+ if (total < *len)
+ PARA_DEBUG_LOG("short write (%d byte(s) left)",
+ *len - total);
+ }
+ *len = total; /* return number actually sent here */
+ return n == -1? -E_SEND : 1; /* return 1 on success */
+}
+
+/**
+ * encrypt and send buffer
+ * @param fd: the file descriptor
+ * @param buf the buffer to be encrypted and sent
+ * @param len the length of \a buf
+ *
+ * Check if encrytion is available. If yes, encrypt the given buffer. Send out
+ * the buffer, encrypted or not, and try to resend the remaing part in case of
+ * short writes.
+ *
+ * @return Positive on success, \p -E_SEND on errors.
+ */
+int send_bin_buffer(int fd, const char *buf, size_t len)
+{
+ int ret;
+
+ if (!len)
+ PARA_CRIT_LOG("%s", "len == 0\n");
+ if (crypt_function_send) {
+ unsigned char *outbuf = para_malloc(len);
+ crypt_function_send(len, (unsigned char *)buf, outbuf);
+ ret = sendall(fd, (char *)outbuf, &len);
+ free(outbuf);
+ } else
+ ret = sendall(fd, buf, &len);
+ return ret;
+}
+
+/**
+ * encrypt and send null terminated buffer.
+ * @param fd the file descriptor
+ * @param buf the null-terminated buffer to be send
+ *
+ * This is equivalent to send_bin_buffer(fd, buf, strlen(buf)).
+ *
+ * @return Positive on success, \p -E_SEND on errors.
+ */
+int send_buffer(int fd, const char *buf)
+{
+ return send_bin_buffer(fd, buf, strlen(buf));
+}
+
+
+/**
+ * send and encrypt a buffer given by a format string
+ * @param fd the file descriptor
+ * @param fmt a format string
+ *
+ * @return Positive on success, \p -E_SEND on errors.
+ */
+__printf_2_3 int send_va_buffer(int fd, char *fmt, ...)
+{
+ char *msg;
+ int ret;
+
+ PARA_VSPRINTF(fmt, msg);
+ ret = send_buffer(fd, msg);
+ free(msg);
+ return ret;
+}
+
+/**
+ * receive and decrypt.
+ *
+ * @param fd the file descriptor
+ * @param buf the buffer to write the decrypted data to
+ * @param size the size of @param buf
+ *
+ * Receive at most \a size bytes from filedescriptor fd. If encrytion is
+ * available, decrypt the received buffer.
+ *
+ * @return the number of bytes received on success. On receive errors, -E_RECV
+ * is returned. On crypt errors, the corresponding crypt error number is
+ * returned.
+ * \sa recv(2)
+ */
+__must_check int recv_bin_buffer(int fd, char *buf, ssize_t size)
+{
+ int n;
+
+ if (crypt_function_recv) {
+ unsigned char *tmp = para_malloc(size);
+ n = recv(fd, tmp, size, 0);
+ if (n > 0)
+ crypt_function_recv(n, tmp, (unsigned char *)buf);
+ free(tmp);
+ } else
+ n = recv(fd, buf, size, 0);
+ if (n == -1)
+ n = -E_RECV;
+ return n;
+}
+
+/**
+ * receive, decrypt and write terminating NULL byte
+ *
+ * @param fd the file descriptor
+ * @param buf the buffer to write the decrypted data to
+ * @param size the size of \a buf
+ *
+ * Read and decrypt at most size - 1 bytes from file descriptor \a fd and write
+ * a NULL byte at the end of the received data.
+ *
+*/
+int recv_buffer(int fd, char *buf, ssize_t size)
+{
+ int n;
+
+ if ((n = recv_bin_buffer(fd, buf, size - 1)) >= 0)
+ buf[n] = '\0';
+ return n;
+}
+
+/**
+ * wrapper around gethostbyname
+ *
+ * @param host hostname or IPv4 address
+ * \return The hostent structure or a NULL pointer if an error occurs
+ * \sa gethostbyname(2)
+ */
+struct hostent *get_host_info(char *host)
+{
+ PARA_INFO_LOG("getting host info of %s\n", host);
+ /* FIXME: gethostbyname() is obsolete */
+ return gethostbyname(host);
+}
+
+/**
+ * a wrapper around socket(2)
+ *
+ * Create an IPv4 socket for sequenced, reliable, two-way, connection-based
+ * byte streams.
+ *
+ * @return The socket fd on success, -E_SOCKET on errors.
+ * \sa socket(2)
+ */
+int get_socket(void)
+{
+ int socket_fd;
+
+ if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
+ return -E_SOCKET;
+ return socket_fd;
+}
+
+/**
+ * a wrapper around connect(2)
+ *
+ * @param fd the file descriptor
+ * @param their_addr the address to connect
+ *
+ * @return \p -E_CONNECT on errors, 1 on success
+ * \sa connect(2)
+ */
+int para_connect(int fd, struct sockaddr_in *their_addr)
+{
+ int ret;
+
+ if ((ret = connect(fd, (struct sockaddr *)their_addr,
+ sizeof(struct sockaddr))) == -1)
+ return -E_CONNECT;
+ return 1;
+}
+
+/**
+ * paraslash's wrapper around the accept system call
+ *
+ * @param fd the listening socket
+ * @param addr structure which is filled in with the address of the peer socket
+ * @param size should contain the size of the structure pointed to by \a addr
+ *
+ * \sa accept(2).
+ */
+int para_accept(int fd, void *addr, size_t size)
+{
+ int new_fd;
+
+ new_fd = accept(fd, (struct sockaddr *) addr, &size);
+ return new_fd == -1? -E_ACCEPT : new_fd;
+}
+
+static int setserversockopts(int socket_fd)
+{
+ int yes = 1;
+
+ if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &yes,
+ sizeof(int)) == -1)
+ return -E_SETSOCKOPT;
+ return 1;
+}
+
+static int do_bind(int socket_fd, struct sockaddr_in *my_addr)
+{
+ if (bind(socket_fd, (struct sockaddr *)my_addr,
+ sizeof(struct sockaddr)) == -1)
+ return -E_BIND;
+ return 1;
+}
+
+/**
+ * prepare a structure for \p AF_UNIX socket addresses
+ *
+ * \param u pointer to the struct to be prepared
+ * \param name the socket pathname
+ *
+ * This just copies \a name to the sun_path component of \a u.
+ *
+ * \return Positive on success, \p -E_NAME_TOO_LONG if \a name is longer
+ * than \p UNIX_PATH_MAX.
+ */
+int init_unix_addr(struct sockaddr_un *u, const char *name)
+{
+ if (strlen(name) >= UNIX_PATH_MAX)
+ return -E_NAME_TOO_LONG;
+ memset(u->sun_path, 0, UNIX_PATH_MAX);
+ u->sun_family = PF_UNIX;
+ strcpy(u->sun_path, name);
+ return 1;
+}
+
+/**
+ * prepare, create, and bind and socket for local communication
+ *
+ * \param name the socket pathname
+ * \param unix_addr pointer to the \p AF_UNIX socket structure
+ * \param mode the desired mode of the socket
+ *
+ * This functions creates a local socket for sequenced, reliable,
+ * two-way, connection-based byte streams.
+ * \sa socket(2)
+ * \sa bind(2)
+ * \sa chmod(2)
+ */
+int create_pf_socket(const char *name, struct sockaddr_un *unix_addr, int mode)
+{
+ int fd, ret;
+
+ fd = socket(PF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0)
+ return -E_SOCKET;
+// unlink(name);
+ ret = init_unix_addr(unix_addr, name);
+ if (ret < 0)
+ return ret;
+ if (bind(fd, (struct sockaddr *) unix_addr, UNIX_PATH_MAX) < 0)
+ return -E_BIND;
+ if (chmod(name, mode) < 0)
+ return -E_CHMOD;
+ return fd;
+}
+
+/**
+ * send NULL terminated buffer and Unix credentials of the current process
+ *
+ * \param sock the socket file descriptor
+ * \param buf the buffer to be sent
+ *
+ * \return On success, this call returns the number of characters sent. On
+ * error, \p -E_SENDMSG ist returned.
+ * \sa okir's Black Hats Manual
+ * \sa sendmsg(2)
+ */
+ssize_t send_cred_buffer(int sock, char *buf)
+{
+ char control[sizeof(struct cmsghdr) + 10];
+ struct msghdr msg;
+ struct cmsghdr *cmsg;
+ static struct iovec iov;
+ struct ucred c;
+ int ret;
+
+ /* Response data */
+ iov.iov_base = buf;
+ iov.iov_len = strlen(buf);
+ c.pid = getpid();
+ c.uid = getuid();
+ c.gid = getgid();
+ /* compose the message */
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = control;
+ msg.msg_controllen = sizeof(control);
+ /* attach the ucred struct */
+ cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_CREDENTIALS;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred));
+ *(struct ucred *)CMSG_DATA(cmsg) = c;
+ msg.msg_controllen = cmsg->cmsg_len;
+ ret = sendmsg(sock, &msg, 0);
+ if (ret < 0)
+ ret = -E_SENDMSG;
+ return ret;
+}
+
+static void dispose_fds(int *fds, int num)
+{
+ int i;
+
+ for (i = 0; i < num; i++)
+ close(fds[i]);
+}
+
+/**
+ * receive a buffer and the Unix credentials of the sending process
+ *
+ * \param fd the socket file descriptor
+ * \param buf the buffer to store the message
+ * \param size the size of \a buffer
+ * \param cred the credentials are returned here
+ *
+ * \sa okir's Black Hats Manual
+ * \sa recvmsg(2)
+ */
+int recv_cred_buffer(int fd, char *buf, size_t size, struct ucred *cred)
+{
+ char control[255];
+ struct msghdr msg;
+ struct cmsghdr *cmsg;
+ struct iovec iov;
+ int result;
+ int yes = 1;
+
+ setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &yes, sizeof(int));
+ memset(&msg, 0, sizeof(msg));
+ memset(buf, 0, size);
+ iov.iov_base = buf;
+ iov.iov_len = size;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = control;
+ msg.msg_controllen = sizeof(control);
+ if (recvmsg(fd, &msg, 0) < 0)
+ return -E_RECVMSG;
+ result = -SCM_CREDENTIALS;
+ cmsg = CMSG_FIRSTHDR(&msg);
+ while (cmsg) {
+ if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type
+ == SCM_CREDENTIALS) {
+ memcpy(cred, CMSG_DATA(cmsg), sizeof(struct ucred));
+ result = iov.iov_len;
+ } else
+ if (cmsg->cmsg_level == SOL_SOCKET
+ && cmsg->cmsg_type == SCM_RIGHTS) {
+ dispose_fds((int *) CMSG_DATA(cmsg),
+ (cmsg->cmsg_len - CMSG_LEN(0))
+ / sizeof(int));
+ }
+ cmsg = CMSG_NXTHDR(&msg, cmsg);
+ }
+ return result;
+}
+
+/** how many pending connections queue will hold */
+#define BACKLOG 10
+
+/**
+ * create a socket, bind it and listen
+ * \param port the tcp port to listen on
+ *
+ * \return The file descriptor of the created socket, negative
+ * on errors.
+ *
+ * \sa get_socket()
+ * \sa setsockopt(2)
+ * \sa bind(2)
+ * \sa listen(2)
+ */
+int init_tcp_socket(int port)
+{
+ int sockfd, ret;
+ struct sockaddr_in my_addr;
+
+ if ((sockfd = get_socket()) < 0)
+ return sockfd;
+ ret = setserversockopts(sockfd);
+ if (ret < 0)
+ return ret;
+ init_sockaddr(&my_addr, port, NULL);
+ ret = do_bind(sockfd, &my_addr);
+ if (ret < 0)
+ return ret;
+ if (listen(sockfd, BACKLOG) == -1)
+ return -E_LISTEN;
+ PARA_INFO_LOG("listening on port %d, fd %d\n", port, sockfd);
+ return sockfd;
+}
+
+/**
+ * receive a buffer and check for a pattern
+ *
+ * \param fd the file descriptor to receive from
+ * \param pattern the expected pattern
+ * \param bufsize the size of the internal buffer
+ *
+ * \return Positive if \a pattern was received, negative otherwise.
+ *
+ * This function creates a buffer of size \a bufsize and tries
+ * to receive at most \a bufsize bytes from file descriptor \a fd.
+ * If at least \p strlen(\a pattern) bytes were received, the beginning of
+ * the received buffer is compared with \a pattern, ignoring case.
+ * \sa recv_buffer()
+ * \sa strncasecmp(3)
+ */
+int recv_pattern(int fd, const char *pattern, size_t bufsize)
+{
+ size_t len = strlen(pattern);
+ char *buf = para_malloc(bufsize + 1);
+ int ret = -E_RECV_PATTERN, n = recv_buffer(fd, buf, bufsize);
+
+ if (n < len)
+ goto out;
+ buf[n] = '\0';
+ if (strncasecmp(buf, pattern, len))
+ goto out;
+ ret = 1;
+out:
+ free(buf);
+ return ret;
+}
--- /dev/null
+/*
+ * Copyright (C) 2006 Andre Noll <maan@systemlinux.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+ */
+
+/** \file net.h exported symbols from net.c */
+
+/**
+ * the buffer size of the sun_path component of struct sockaddr_un
+ *
+ * While glibc doesn't define \p UNIX_PATH_MAX, it
+ * documents it has being limited to 108 bytes.
+ */
+#ifndef UNIX_PATH_MAX
+#define UNIX_PATH_MAX 108
+#endif
+
+struct hostent *get_host_info(char *);
+int get_socket(void);
+void init_sockaddr(struct sockaddr_in*, int, const struct hostent*);
+int para_connect(int, struct sockaddr_in *);
+int send_buffer(int, const char *);
+int send_bin_buffer(int, const char *, size_t);
+int send_va_buffer(int, char *, ...);
+int recv_buffer(int, char *, ssize_t);
+int recv_bin_buffer(int, char *, ssize_t);
+int para_accept(int, void *addr, size_t size);
+int create_pf_socket(const char *, struct sockaddr_un *, int mod);
+int init_unix_addr(struct sockaddr_un *, const char *);
+int recv_cred_buffer(int, char *, size_t, struct ucred *);
+ssize_t send_cred_buffer(int, char*);
+int recv_pattern(int fd, const char *pattern, size_t bufsize);
+int init_tcp_socket(int port);
+
--- /dev/null
+/*
+ * Copyright (C) 2004-2006 Andre Noll <maan@systemlinux.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+ */
+/** \file ogg.c para_server's ogg vorbis audio format handler */
+
+#include <ogg/ogg.h>
+#include <vorbis/codec.h>
+#include <vorbis/vorbisfile.h>
+
+#include "server.cmdline.h"
+#include "server.h"
+#include "afs.h"
+#include "error.h"
+#include "string.h"
+
+/* must be big enough to hold header */
+#define CHUNK_SIZE 32768
+static double chunk_time = 0.25;
+
+static OggVorbis_File *oggvorbis_file;
+static FILE *infile;
+static int header_len, oggbuf_len, vi_channels;
+static char *header, *oggbuf;
+static ogg_int64_t *chunk_table, max_chunk_len;
+struct audio_format *af;
+static long vi_sampling_rate, vi_bitrate, vi_bitrate_nominal,
+ num_chunks;
+
+static int ogg_compute_header_len(void)
+{
+ int ret, len, in = fileno(infile);
+ unsigned int serial;
+ char *buf;
+ ogg_page page;
+ ogg_packet packet;
+ vorbis_comment vc;
+ vorbis_info vi;
+ ogg_stream_state *stream_in = para_malloc(sizeof(ogg_stream_state));
+ ogg_stream_state *stream_out = para_malloc(sizeof(ogg_stream_state));
+ ogg_sync_state *sync_in = para_malloc(sizeof(ogg_sync_state));
+
+ ogg_sync_init(sync_in);
+ vorbis_info_init(&vi);
+ vorbis_comment_init(&vc);
+ buf = ogg_sync_buffer(sync_in, CHUNK_SIZE);
+ len = read(in, buf, CHUNK_SIZE);
+ ret = -E_OGG_READ;
+ if (len <= 0)
+ goto err1;
+ ogg_sync_wrote(sync_in, len);
+ ret = -E_SYNC_PAGEOUT;
+ if (ogg_sync_pageout(sync_in, &page) <= 0)
+ goto err1;
+ serial = ogg_page_serialno(&page);
+ ogg_stream_init(stream_in, serial);
+ ogg_stream_init(stream_out, serial);
+ ret = ogg_stream_pagein(stream_in, &page);
+ if (ret < 0) {
+ ret = E_STREAM_PAGEIN;
+ goto err2;
+ }
+ ret = ogg_stream_packetout(stream_in, &packet);
+ if (ret != 1) {
+ ret = -E_STREAM_PACKETOUT;
+ goto err2;
+ }
+ ret = -E_VORBIS;
+ if (vorbis_synthesis_headerin(&vi, &vc, &packet) < 0) {
+ goto err2;
+ } else
+ PARA_INFO_LOG("channels: %i, rate: %li\n", vi.channels,
+ vi.rate);
+ ogg_stream_packetin(stream_out, &packet);
+
+ ret = ogg_sync_pageout(sync_in, &page);
+ if (ret <= 0) {
+ ret = -E_SYNC_PAGEOUT;
+ goto err2;
+ }
+ ogg_stream_pagein(stream_in, &page);
+ ogg_stream_packetout(stream_in, &packet);
+ ogg_stream_packetin(stream_out, &packet);
+
+ ret = ogg_sync_pageout(sync_in, &page);
+ if (ret <= 0) {
+ ret = -E_SYNC_PAGEOUT;
+ goto err2;
+ }
+ ogg_stream_pagein(stream_in, &page);
+ ogg_stream_packetout(stream_in, &packet);
+ ogg_stream_packetin(stream_out, &packet);
+
+ header_len = 0;
+ while (ogg_stream_flush(stream_out, &page))
+ header_len += page.body_len + page.header_len;
+ ret = len;
+ PARA_INFO_LOG("header_len = %d\n", header_len);
+err2:
+ ogg_stream_destroy(stream_in);
+ ogg_stream_destroy(stream_out);
+err1:
+ ogg_sync_destroy(sync_in);
+ vorbis_info_clear(&vi);
+ vorbis_comment_clear(&vc);
+ return ret;
+}
+
+static void tunetable(void)
+{
+ int i = 1, j = -1, lp = 1;
+ while (i < num_chunks) {
+ if (chunk_table[i] == chunk_table[lp]) {
+ i++;
+ continue;
+ }
+ if (j < 0)
+ tv_scale(i + 2, &af->chunk_tv, &af->eof_tv);
+ for (j = lp; j < i; j++)
+ chunk_table[j] = chunk_table[i];
+ lp = i;
+ }
+#if 1
+ for (i = 2; i < num_chunks; i++)
+ if (chunk_table[i] != chunk_table[1])
+ break;
+ lp = i;
+ for (i = 2; i < num_chunks - lp; i++)
+ chunk_table[i] = chunk_table[i + lp];
+#endif
+}
+
+
+/*
+ * Alloc and fill array table of byte offsets. chunk_table[i] is the
+ * offset in the current infile at which the sample containing time i *
+ * CHUNK_TIME begins.
+ */
+static void ogg_compute_chunk_table(double time_total)
+{
+ int i, ret, num;
+ ogg_int64_t pos = 0, min = 0, old_pos;
+
+ old_pos = 0;
+ ret = 0;
+ num = time_total / chunk_time + 3;
+ PARA_DEBUG_LOG("chunk time: %g allocating %d chunk pointers\n",
+ chunk_time, num);
+ chunk_table = para_malloc(num * sizeof(ogg_int64_t));
+ chunk_table[0] = 0;
+ max_chunk_len = 0;
+ rewind(infile);
+ for (i = 1; ret == 0; i++) {
+ ogg_int64_t diff;
+ ret = ov_time_seek(oggvorbis_file, i * chunk_time);
+ if (ret)
+ break;
+ pos = ov_raw_tell(oggvorbis_file);
+ diff = pos - old_pos;
+ max_chunk_len = MAX(max_chunk_len, diff);
+ min = (i == 1)? diff : MIN(min, diff);
+ chunk_table[i] = pos;
+ if (i < 11 || !((i - 1) % 1000)|| i > num - 11)
+ PARA_DEBUG_LOG("chunk #%d: %g secs, pos: %lli, "
+ "size: %lli\n", i - 1,
+ i * chunk_time, pos, pos - old_pos);
+ old_pos = pos;
+ }
+ num_chunks = i - 1;
+ chunk_table[i] = pos;
+ tunetable();
+ PARA_INFO_LOG("%li chunks (%fs), max chunk: %lli, min chunk: %lli\n",
+ num_chunks, chunk_time, max_chunk_len, min);
+ rewind(infile);
+}
+
+static void ogg_close_audio_file(void)
+{
+ if (oggvorbis_file) {
+ PARA_DEBUG_LOG("%s", "ov_clear\n");
+ ov_clear(oggvorbis_file);
+ free(oggvorbis_file);
+ oggvorbis_file = NULL;
+ }
+ free(header);
+ header = NULL;
+ header_len = 0;
+ free(chunk_table);
+ chunk_table = NULL;
+ num_chunks = 0;
+ free(oggbuf);
+ oggbuf = NULL;
+ oggbuf_len = 0;
+}
+
+static int ogg_save_header(FILE *file, int header_len)
+{
+ int ret;
+
+ header = para_malloc(header_len);
+ rewind(file);
+ ret = read(fileno(file), header, header_len);
+ if (ret != header_len)
+ return -E_OGG_READ;
+ return 1;
+}
+
+/*
+ * Init oggvorbis file and write some tech data to given pointers.
+ */
+static int ogg_get_file_info(FILE *file, char *info_str, long unsigned *frames,
+ int *seconds)
+{
+ int ret;
+ double time_total;
+ vorbis_info *vi;
+ ogg_int64_t raw_total;
+
+ infile = file;
+ if (!file)
+ return -E_OGG_NO_FILE;
+ ret = ogg_compute_header_len();
+ if (ret < 0)
+ return ret;
+ ret = ogg_save_header(file, header_len);
+ if (ret < 0)
+ return ret;
+ rewind(file);
+ oggvorbis_file = para_malloc(sizeof(OggVorbis_File));
+ ret = ov_open(file, oggvorbis_file, NULL, 0);
+ if (ret < 0) {
+ free(oggvorbis_file);
+ free(header);
+ return -E_OGG_OPEN;
+ }
+ ret = -E_OGG_INFO;
+ vi = ov_info(oggvorbis_file, 0);
+ if (!vi)
+ goto err;
+ time_total = ov_time_total(oggvorbis_file, -1);
+ raw_total = ov_raw_total(oggvorbis_file, -1);
+ *seconds = time_total;
+ vi_sampling_rate = vi->rate;
+ vi_bitrate = ov_bitrate(oggvorbis_file, 0);
+ vi_bitrate_nominal = vi->bitrate_nominal;
+ vi_channels = vi->channels;
+ ogg_compute_chunk_table(time_total);
+ *frames = num_chunks;
+ sprintf(info_str, "audio_file_info1:%lu x %lu, %ldkHz, %d channels, %ldkbps\n"
+ "audio_file_info2: \n"
+ "audio_file_info3: \n",
+ num_chunks, (long unsigned) (chunk_time * 1000 * 1000),
+ vi_sampling_rate / 1000, vi_channels, vi_bitrate / 1000
+ );
+ rewind(file);
+ return 1;
+err:
+ ogg_close_audio_file();
+ return ret;
+}
+
+/*
+ * Simple stream reposition routine
+ */
+static int ogg_reposition_stream(long unsigned request)
+{
+ int ret;
+ long seek;
+
+ seek = chunk_table[request];
+ ret = fseek(infile, seek, SEEK_SET);
+ PARA_DEBUG_LOG("seek to %li returned %d. offset:%li\n", seek,
+ ret, ftell(infile));
+ return ret < 0? -E_OGG_REPOS : 1;
+}
+
+static ogg_int64_t get_chunk_size(long unsigned chunk_num)
+{
+ ogg_int64_t ret;
+ if (chunk_num >= num_chunks)
+ return -1;
+ ret = chunk_table[chunk_num + 1] - chunk_table[chunk_num];
+ if (!ret)
+ return ret;
+#if 0
+ PARA_DEBUG_LOG("chunk %d: %lli-%lli (%lli bytes)\n",
+ chunk_num,
+ chunk_table[chunk_num],
+ chunk_table[chunk_num + 1] - 1,
+ ret);
+#endif
+ return ret;
+}
+
+char *ogg_read_chunk(long unsigned current_chunk, ssize_t *len)
+{
+ int ret;
+ ogg_int64_t cs = get_chunk_size(current_chunk);
+ if (!cs) { /* nothing to send for this run */
+ *len = 0;
+ return oggbuf;
+ }
+ if (cs < 0) { /* eof */
+ *len = 0;
+ return NULL;
+ }
+ *len = cs;
+ if (!oggbuf || oggbuf_len < *len) {
+ PARA_INFO_LOG("increasing ogg buffer size (%d -> %u)\n",
+ oggbuf_len, *len);
+ oggbuf = para_realloc(oggbuf, *len);
+ oggbuf_len = *len;
+ }
+ ret = read(fileno(infile), oggbuf, *len);
+ if (!ret) {
+ *len = 0;
+ return NULL;
+ }
+ if (ret < 0) {
+ *len = -E_OGG_READ;
+ return NULL;
+ }
+ if (ret != *len)
+ PARA_WARNING_LOG("short read (%d/%d)\n", ret, *len);
+ *len = ret;
+ return oggbuf;
+}
+
+static char *ogg_get_header_info(int *len)
+{
+ *len = header_len;
+ return header;
+}
+
+void ogg_init(void *p)
+{
+ af = p;
+ af->reposition_stream = ogg_reposition_stream;
+ af->get_file_info = ogg_get_file_info,
+ af->read_chunk = ogg_read_chunk;
+ af->close_audio_file = ogg_close_audio_file;
+ af->get_header_info = ogg_get_header_info;
+ af->chunk_tv.tv_sec = 0;
+ af->chunk_tv.tv_usec = 250 * 1000;
+ tv_scale(3, &af->chunk_tv, &af->eof_tv);
+}
--- /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 oggdec.c paraslash's ogg vorbis decoder */
+
+#include "gcc-compat.h"
+#include "para.h"
+
+#include "oggdec_filter.cmdline.h"
+#include "list.h"
+#include "filter.h"
+#include "error.h"
+#include "string.h"
+
+#include <vorbis/vorbisfile.h>
+
+/** \cond some internal constants */
+#define BITS 16
+#define ENDIAN 0
+#define SIGN 1
+/** \endcond */
+
+/** data specific to the oggdec filter */
+struct private_oggdec_data {
+ /** describes an ogg vorbis file */
+ OggVorbis_File *vf;
+ /** the input buffer */
+ char *inbuf;
+ /** the length of \a inbuf */
+ size_t inbuf_len;
+ /** the number of bytes consumed from the input buffer */
+ size_t converted;
+};
+
+static size_t cb_read(void *buf, size_t size, size_t nmemb, void *datasource)
+{
+ struct filter_node *fn = datasource;
+ struct private_oggdec_data *pod = fn->private_data;
+ size_t ret, have = pod->inbuf_len - pod->converted;
+ char *p = pod->inbuf + pod->converted;
+
+ if (*fn->fci->eof)
+ return 0;
+// PARA_DEBUG_LOG("pod = %p\n", pod);
+// PARA_DEBUG_LOG("vorbis requests %d bytes, have %d\n", size * nmemb, have);
+ if (pod->inbuf_len < size) {
+ errno = EAGAIN;
+ return -1;
+ }
+ ret = MIN(nmemb, have / size) * size;
+ memcpy(buf, p, ret);
+ pod->converted += ret;
+ return ret;
+}
+
+/*
+ * cb_seek -- custom data seeking function
+ * Since we want the data source to be treated as unseekable at all
+ * times, the provided seek callback always returns -1 (failure).
+ */
+static int cb_seek(__unused void *datasource, __unused ogg_int64_t offset,
+ __unused int whence)
+{
+ return -1;
+}
+
+static int cb_close(__unused void *datasource)
+{
+ return 0;
+}
+
+static const ov_callbacks ovc = {
+ .read_func = cb_read,
+ .seek_func = cb_seek,
+ .close_func = cb_close,
+ /*
+ * The tell function need not be provided if
+ * the data IO abstraction is not seekable
+ */
+ .tell_func = NULL
+};
+
+static void ogg_open(struct filter_node *fn)
+{
+ struct private_oggdec_data *pod = para_calloc(
+ sizeof(struct private_oggdec_data));
+ struct gengetopt_args_info *conf = fn->conf;
+
+ fn->private_data = pod;
+ fn->bufsize = conf->bufsize_arg * 1024;
+ fn->buf = para_malloc(fn->bufsize);
+}
+
+static void ogg_close(struct filter_node *fn)
+{
+ struct private_oggdec_data *pod = fn->private_data;
+ if (pod->vf) {
+ PARA_DEBUG_LOG("ov_clearing %p, pod = %p\n", pod->vf, pod);
+ ov_clear(pod->vf);
+ free(pod->vf);
+ pod->vf = NULL;
+ } else
+ PARA_DEBUG_LOG("nothing to close in fc %p, pod = %p\n", pod->vf, pod);
+ free(fn->buf);
+ fn->buf = NULL;
+ free(fn->private_data);
+ fn->private_data = NULL;
+}
+
+static ssize_t ogg_convert(char *inbuffer, size_t len, struct filter_node *fn)
+{
+ ssize_t ret;
+ struct private_oggdec_data *pod = fn->private_data;
+ struct gengetopt_args_info *conf = fn->conf;
+ /* make the buffer known to the read callback cb_read() */
+ pod->inbuf = inbuffer;
+ pod->inbuf_len = len;
+ pod->converted = 0;
+
+ if (!pod->vf) {
+ int ib = 1024 * conf->initial_buffer_arg; /* initial buffer */
+ if (len <ib && !*fn->fci->eof && !fn->fci->error) {
+ PARA_INFO_LOG("initial input buffer %d/%d, waiting for more data\n",
+ len, ib);
+ return 0;
+ }
+ pod->vf = para_malloc(sizeof(struct OggVorbis_File));
+ PARA_NOTICE_LOG("input buffer: %d, opening ov callbacks\n", len);
+ ret = ov_open_callbacks(fn, pod->vf,
+ NULL, /* no initial buffer */
+ 0, /* no initial bytes */
+ ovc); /* the ov_open_callbacks */
+ if (ret == OV_EREAD)
+ return -E_OGGDEC_READ;
+ if (ret == OV_ENOTVORBIS)
+ return -E_OGGDEC_NOTVORBIS;
+ if (ret == OV_EVERSION)
+ return -E_OGGDEC_VERSION;
+ if (ret == OV_EBADHEADER)
+ return -E_OGGDEC_BADHEADER;
+ if (ret < 0)
+ return -E_OGGDEC_FAULT;
+ fn->fci->channels = ov_info(pod->vf, 0)->channels;
+ fn->fci->samplerate = ov_info(pod->vf, 0)->rate;
+ PARA_NOTICE_LOG("%d channels, %d Hz\n", fn->fci->channels, fn->fci->samplerate);
+ }
+again:
+ ret = ov_read(pod->vf, fn->buf + fn->loaded, fn->bufsize - fn->loaded,
+ ENDIAN, BITS / 8, SIGN, NULL);
+ if (ret == OV_HOLE || !ret) {
+ return pod->converted;
+ }
+ if (ret < 0)
+ return -E_OGGDEC_BADLINK;
+ fn->loaded += ret;
+ if (!*fn->fci->eof && !fn->fci->error && fn->loaded < fn->bufsize)
+ goto again;
+ return pod->converted;
+}
+
+static void *oggdec_parse_config(int argc, char **argv)
+{
+ struct gengetopt_args_info *ret = para_calloc(sizeof(struct gengetopt_args_info));
+ if (!oggdec_cmdline_parser(argc, argv, ret))
+ return ret;
+ free(ret);
+ return NULL;
+}
+
+/** the init function of the ogg vorbis decoder */
+void oggdec_init(struct filter *f)
+{
+ f->open = ogg_open;
+ f->close = ogg_close;
+ f->convert = ogg_convert;
+ f->print_help = oggdec_cmdline_parser_print_help;
+ f->parse_config = oggdec_parse_config;
+}
--- /dev/null
+option "bufsize" b "size of output buffer" int typestr="kilobyte" default="128" no
+option "initial_buffer" i "size of initial input buffer" int typestr="kilobyte" default="16" no
--- /dev/null
+/** \file ortp.h some macros used by ortp_send.c and ortp_recv.c*/
+enum ortp_audio_packet_type {ORTP_EOF, ORTP_BOF, ORTP_HEADER, ORTP_DATA};
+
+#define ORTP_AUDIO_HEADER_LEN 10
+
+#define WRITE_PACKET_TYPE(buf, x) (buf)[0] = (unsigned char)((x)&0xff)
+#define READ_PACKET_TYPE(buf) (unsigned)(buf)[0]
+
+#define WRITE_CHUNK_TIME(buf, x) (buf)[1] = (unsigned char)((x)&0xff);\
+ (buf)[2] = (unsigned char)(((x)>>8)&0xff);\
+ (buf)[3] = (unsigned char)(((x)>>16)&0xff);\
+ (buf)[4] = (unsigned char)(((x)>>24)&0xff);
+#define READ_CHUNK_TIME(buf) (unsigned char)(buf)[1] + \
+ ((unsigned char)(buf)[2] << 8) + \
+ ((unsigned char)(buf)[3] << 16) + \
+ ((unsigned char)(buf)[4] << 24)
+
+#define WRITE_CHUNK_TS(buf, x) (buf)[5] = (unsigned char)((x) & 0xff); \
+ (buf)[6] = (unsigned char)(((x >> 8) & 0xff));
+#define READ_CHUNK_TS(buf) (unsigned char)(buf)[5] + \
+ ((unsigned char)(buf)[6] << 8)
+
+#define WRITE_STREAM_TYPE(buf, x) (buf)[7] = (unsigned char)((x)&0xff)
+#define READ_STREAM_TYPE(buf) (unsigned)(buf)[7]
+
+#define WRITE_HEADER_LEN(buf, x) (buf)[8] = (unsigned char)((x) & 0xff); \
+ (buf)[9] = (unsigned char)(((x >> 8) & 0xff));
+#define READ_HEADER_LEN(buf) (unsigned char)(buf)[8] + \
+ ((unsigned char)(buf)[9] << 8)
+
--- /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 ortp_recv.c paraslash's ortp receiver */
+
+#include "para.h"
+#include <ortp/ortp.h>
+
+#include "ortp.h"
+#include "recv.h"
+#include "ortp_recv.cmdline.h"
+
+#include "error.h"
+#include "audiod.h"
+#include "string.h"
+
+#define CHUNK_SIZE 128 * 1024
+
+extern gint msg_to_buf(mblk_t *, char *, gint);
+
+/**
+ * data specific to the ortp receiver
+ *
+ * \sa receiver receiver_node
+ */
+struct private_ortp_recv_data {
+
+/**
+ *
+ *
+ * whether a header was received
+ *
+ * A flag indicating whether the ortp receiver already received a packet which
+ * contains the audio file header.
+ *
+ * This flag has no effect if the audio stream indicates that no extra headers
+ * will be sent (mp3). Otherwise, all data packets are dropped until the
+ * header is received.
+ */
+int have_header;
+/** the ortp session this instance of the receiver belongs to */
+RtpSession *session;
+/** the time the first data or header packet was received */
+struct timeval start;
+/** ETA of the next chunk */
+struct timeval next_chunk;
+/** number of consecutive bad chunks */
+int c_bad;
+/** the current timestamp which is passed to the receiving function of libortp */
+guint32 timestamp;
+/** \a timestamp increases by this amount */
+guint32 chunk_ts;
+};
+
+#if 1
+static void recalc_timestamp(struct receiver_node *rn)
+{
+ struct timeval now, tmp, min_delay = {0, 5000};
+ struct private_ortp_recv_data *pord = rn->private_data;
+
+ gettimeofday(&now, NULL);
+ tv_add(&now, &min_delay, &pord->next_chunk);
+ if (pord->start.tv_sec) {
+ int new_ts;
+
+ tv_diff(&now, &pord->start, &tmp);
+ new_ts = rtp_session_time_to_ts(pord->session, tv2ms(&tmp));
+// if (pord->c_bad > 50)
+ pord->timestamp = new_ts;
+ } else
+ pord->timestamp = 0;
+ PARA_DEBUG_LOG("ts=%u, c_bad = %d\n", pord->timestamp, pord->c_bad);
+
+}
+#endif
+
+static int ortp_recv_pre_select(struct receiver_node *rn,
+ __unused fd_set *rfds, __unused fd_set *wfds,
+ struct timeval *timeout)
+{
+ struct private_ortp_recv_data *pord = rn->private_data;
+ struct timeval now, tmp;
+
+ gettimeofday(&now, NULL);
+ if (tv_diff(&now, &pord->next_chunk, &tmp) >= 0) {
+ tmp.tv_sec = 0;
+ tmp.tv_usec = 1000;
+ }
+ if (tv_diff(timeout, &tmp, NULL) > 0)
+ *timeout = tmp;
+ return -1; /* we did not modify the fd sets */
+}
+
+static void compute_next_chunk(struct timeval *now, unsigned chunk_time,
+ struct private_ortp_recv_data *pord)
+{
+ struct timeval chunk_tv = {0, chunk_time};
+// if (!pord->start.tv_sec) {
+// pord->start = *now;
+// tv_add(&chunk_tv, now, &pord->next_chunk);
+// } else
+ {
+ struct timeval tmp;
+ tv_add(&chunk_tv, &pord->next_chunk, &tmp);
+ pord->next_chunk = tmp;
+ }
+ pord->timestamp += pord->chunk_ts;
+ PARA_DEBUG_LOG("next chunk (ts = %d) due at %lu:%lu\n",
+ pord->timestamp, pord->next_chunk.tv_sec,
+ pord->next_chunk.tv_usec);
+}
+/** \cond */
+#define BUF_TO_VAL(buf) (((unsigned)(buf)[1] & 0xff) + 256 * ((unsigned)(buf)[0] & 0xff))
+/** \endcond */
+
+
+static int ortp_recv_post_select(struct receiver_node *rn, int select_ret,
+ __unused fd_set *rfds, __unused fd_set *wfds)
+{
+ struct private_ortp_recv_data *pord = rn->private_data;
+ mblk_t *mp;
+ int ret, packet_type, stream_type;
+ char tmpbuf[CHUNK_SIZE + 3];
+ struct timeval now;
+ unsigned chunk_time;
+
+ gettimeofday(&now, NULL);
+ PARA_DEBUG_LOG("rn: %p, pord: %p, session: %p\n", rn, pord, pord->session);
+ if (pord->start.tv_sec) {
+ struct timeval diff;
+ if (tv_diff(&now, &pord->next_chunk, &diff) < 0) {
+ long unsigned diff_ms = tv2ms(&diff);
+ if (diff_ms > 5) {
+ PARA_DEBUG_LOG("too early: %lu ms left\n",
+ diff_ms);
+ return 1;
+ }
+ }
+ }
+ mp = rtp_session_recvm_with_ts(pord->session, pord->timestamp);
+ if (!mp) {
+ pord->c_bad++;
+ if ((pord->c_bad > 500 && pord->start.tv_sec) || pord->c_bad > 10000)
+ return -E_TOO_MANY_BAD_CHUNKS;
+// PARA_NOTICE_LOG("did not receive buffer, ts=%u, c_bad = %d, to = %lu\n",
+// pord->timestamp, pord->c_bad, tv2ms(&tmp));
+ if (pord->c_bad > 100)
+ recalc_timestamp(rn);
+ return 1;
+ }
+ /* okay, we have a chunk of data */
+ PARA_DEBUG_LOG("have it ts = %d, chunk_ts = %d, loaded: %d, bad: %d\n",
+ pord->timestamp, pord->chunk_ts, rn->loaded, pord->c_bad);
+ if (!pord->start.tv_sec)
+ pord->start = now;
+ ret = msg_to_buf(mp, tmpbuf, CHUNK_SIZE);
+ if (ret < ORTP_AUDIO_HEADER_LEN) {
+ if (ret < 0)
+ ret = -E_MSG_TO_BUF;
+ else
+ ret = 0;
+ goto err_out;
+ }
+ packet_type = READ_PACKET_TYPE(tmpbuf);
+ stream_type = READ_STREAM_TYPE(tmpbuf);
+ chunk_time = READ_CHUNK_TIME(tmpbuf);
+ pord->chunk_ts = READ_CHUNK_TS(tmpbuf);
+ PARA_DEBUG_LOG("packet type: %d, stream type: %d, chunk time: %u, chunk_ts: %u\n", packet_type,
+ stream_type, chunk_time, pord->chunk_ts);
+ switch (packet_type) {
+ unsigned header_len, payload_len;
+ case ORTP_EOF:
+ ret = 0;
+ goto err_out;
+ case ORTP_BOF:
+ PARA_INFO_LOG("bof (%d)\n", ret);
+ pord->have_header = 1;
+ /* fall through */
+ case ORTP_DATA:
+ if (!pord->have_header && stream_type) /* can't use the data, wait for header */
+ goto success;
+ if (ret + rn->loaded >= CHUNK_SIZE + ORTP_AUDIO_HEADER_LEN) {
+ ret = -E_OVERRUN;
+ goto err_out;
+ }
+ if (ret > ORTP_AUDIO_HEADER_LEN) {
+ memcpy(rn->buf + rn->loaded, tmpbuf + ORTP_AUDIO_HEADER_LEN,
+ ret - ORTP_AUDIO_HEADER_LEN);
+ rn->loaded += ret - ORTP_AUDIO_HEADER_LEN;
+ }
+ goto success;
+ case ORTP_HEADER:
+ header_len = READ_HEADER_LEN(tmpbuf);
+ PARA_DEBUG_LOG("header packet (%d bytes), header len: %d\n", ret, header_len);
+ if (!pord->have_header) {
+ pord->have_header = 1;
+ memcpy(rn->buf, tmpbuf + ORTP_AUDIO_HEADER_LEN,
+ ret - ORTP_AUDIO_HEADER_LEN);
+ rn->loaded = ret - ORTP_AUDIO_HEADER_LEN;
+ goto success;
+ }
+ if (header_len + ORTP_AUDIO_HEADER_LEN > ret) {
+ ret = -E_INVALID_HEADER;
+ goto err_out;
+ }
+ payload_len = ret - ORTP_AUDIO_HEADER_LEN - header_len;
+// PARA_INFO_LOG("len: %d header_len: %d, payload_len: %d, loaded: %d\n", ret,
+// header_len, payload_len, rn->loaded);
+ if (rn->loaded + payload_len > CHUNK_SIZE) {
+ ret = -E_OVERRUN;
+ goto err_out;
+ }
+ if (payload_len)
+ memcpy(rn->buf + rn->loaded, tmpbuf
+ + (ret - payload_len), payload_len);
+ rn->loaded += payload_len;
+ goto success;
+ }
+success:
+ freemsg(mp);
+ pord->c_bad = 0;
+ compute_next_chunk(&now, chunk_time, pord);
+ return 1;
+err_out:
+ freemsg(mp);
+ return ret;
+}
+
+static void ortp_shutdown(void)
+{
+// ortp_global_stats_display();
+ ortp_exit();
+}
+
+static void ortp_recv_close(struct receiver_node *rn)
+{
+ struct private_ortp_recv_data *pord = rn->private_data;
+
+ rtp_session_destroy(pord->session);
+ free(rn->private_data);
+ free(rn->buf);
+}
+
+static void *ortp_recv_parse_config(int argc, char **argv)
+{
+ int ret;
+
+ struct gengetopt_args_info *tmp = para_calloc(sizeof(struct gengetopt_args_info));
+
+ ret = ortp_recv_cmdline_parser(argc, argv, tmp)? -E_ORTP_SYNTAX : 1;
+ if (ret > 0)
+ return tmp;
+ free(tmp);
+ return NULL;
+}
+
+static int ortp_recv_open(struct receiver_node *rn)
+{
+ struct private_ortp_recv_data *pord;
+ struct gengetopt_args_info *conf = rn->conf;
+
+ rn->buf = para_calloc(CHUNK_SIZE);
+
+ rn->private_data = para_calloc(sizeof(struct private_ortp_recv_data));
+ pord = rn->private_data;
+ pord->session = rtp_session_new(RTP_SESSION_RECVONLY);
+ PARA_NOTICE_LOG("receiving from %s:%d\n", conf->host_arg, conf->port_arg);
+ rtp_session_set_local_addr(pord->session, conf->host_arg, conf->port_arg);
+ rtp_session_set_payload_type(pord->session, PAYLOAD_AUDIO_CONTINUOUS);
+ return 1;
+}
+
+/**
+ * the init function of the ortp receiver
+ *
+ * \param r pointer to the receiver struct to initialize
+ *
+ * Initialize all function pointers of \a r and call libortp's ortp_init().
+ */
+void ortp_recv_init(struct receiver *r)
+{
+ r->shutdown = ortp_shutdown;
+ r->open = ortp_recv_open;
+ r->close = ortp_recv_close;
+ r->pre_select = ortp_recv_pre_select;
+ r->post_select = ortp_recv_post_select;
+ r->parse_config = ortp_recv_parse_config;
+
+ ortp_init();
+ ortp_set_debug_file("oRTP", NULL);
+}
--- /dev/null
+section "ortp options"
+option "host" i "ip or host to receive rtp packets from" string default="224.0.1.38" no
+option "port" p "udp port." int typestr="portnumber" default="1500" no
--- /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 ortp_send.c para_server's ortp sender */
+
+
+#include "server.cmdline.h"
+#include "server.h"
+#include "afs.h"
+#include "send.h"
+#include "list.h"
+#include <ortp/ortp.h>
+#include "ortp.h"
+#include "string.h"
+
+/** \cond convert in_addr to ascii */
+#define TARGET_ADDR(oc) inet_ntoa((oc)->addr)
+/** \endcond */
+
+extern struct gengetopt_args_info conf;
+
+/** describes one entry in the list of targets for the ortp sender */
+struct ortp_target {
+/** address info */
+ struct in_addr addr;
+/** whether the ortp sender is activated */
+ int status;
+/** the ortp timestamp increases by this amount */
+ int chunk_ts;
+/** the currently used timestamp for this target */
+ int last_ts;
+/** the position of this target in the list of targets */
+ struct list_head node;
+/** the UDP port */
+ int port;
+/** non-zero if at least one chunk has been sent to this target */
+ int streaming;
+/** the session pointer from libortp */
+ RtpSession *session;
+};
+
+static int numtargets;
+static struct list_head targets;
+static struct sender *self;
+
+static void ortp_delete_target(struct ortp_target *ot, const char *msg)
+{
+ PARA_NOTICE_LOG("deleting %s:%d (%s) from list\n", TARGET_ADDR(ot),
+ ot->port, msg);
+ if (ot->session) {
+ rtp_session_destroy(ot->session);
+ ot->session = NULL;
+ }
+ numtargets--;
+ list_del(&ot->node);
+ free(ot);
+}
+
+static void ortp_send_buf(char *buf, int len, long unsigned chunks_sent)
+{
+ struct ortp_target *ot, *tmp;
+ int ret;
+
+ list_for_each_entry_safe(ot, tmp, &targets, node) {
+ struct timeval now;
+ int ts;
+ gettimeofday(&now, NULL);
+ if (!ot->session)
+ continue;
+ WRITE_CHUNK_TS(buf, ot->chunk_ts);
+ ts = ot->chunk_ts * chunks_sent;
+ ret = rtp_session_send_with_ts(ot->session, buf, len, ts);
+ ot->last_ts = ts;
+ if (ret < 0)
+ ortp_delete_target(ot, "send error");
+ if (ret != len +12)
+ PARA_NOTICE_LOG("short write %d\n", ret);
+ }
+}
+
+static void ortp_init_session(struct ortp_target *ot)
+{
+ RtpSession *s;
+ int ret;
+
+ PARA_NOTICE_LOG("sending to udp %s:%d\n", TARGET_ADDR(ot), ot->port);
+ ot->session = rtp_session_new(RTP_SESSION_SENDONLY);
+ if (!ot->session)
+ return;
+ s = ot->session;
+// rtp_session_set_jitter_compensation(ot->session, 100);
+ /* always successful */
+ rtp_session_set_send_payload_type(s, PAYLOAD_AUDIO_CONTINUOUS);
+ ret = rtp_session_set_remote_addr(s, TARGET_ADDR(ot), ot->port);
+ if (ret < 0) {
+ rtp_session_destroy(ot->session);
+ ot->session = NULL;
+ }
+}
+
+/* called by afs */
+static void ortp_shutdown_targets(void)
+{
+ char buf[2];
+ struct ortp_target *ot, *tmp;
+
+ buf[0] = ORTP_EOF;
+ list_for_each_entry_safe(ot, tmp, &targets, node) {
+ if (!ot->session || !ot->streaming)
+ continue;
+ PARA_INFO_LOG("sending eof to ortp target %s:%d, ts = %d\n", TARGET_ADDR(ot), ot->port,
+ ot->last_ts);
+ rtp_session_send_with_ts(ot->session, buf, 1, ot->last_ts);
+ ot->streaming = 0;
+ ot->chunk_ts = 0;
+ rtp_session_reset(ot->session);
+ }
+}
+
+static int need_extra_header(struct audio_format *af, long unsigned chunks_sent)
+{
+ /* FIXME: No need to compute this on every run */
+ int mod = conf.ortp_header_interval_arg / (tv2ms(&af->chunk_tv) + 1);
+ if (mod && (chunks_sent % mod))
+ return 0;
+ return 1;
+}
+
+static void ortp_send(struct audio_format *af, long unsigned current_chunk,
+ long unsigned chunks_sent, const char *buf, size_t len)
+{
+ struct ortp_target *ot, *tmp;
+ size_t sendbuf_len;
+ size_t header_len = 0;
+ int packet_type = ORTP_DATA, stream_type = af && af->get_header_info; /* header stream? */
+ char *sendbuf, *header_buf = NULL;
+
+ if (self->status != SENDER_ON)
+ return;
+ list_for_each_entry_safe(ot, tmp, &targets, node) {
+ if (!ot->session) {
+ ortp_init_session(ot);
+ if (!ot->session)
+ continue;
+ }
+ if (!ot->chunk_ts)
+ ot->chunk_ts = rtp_session_time_to_ts(ot->session, tv2ms(&af->chunk_tv));
+// PARA_DEBUG_LOG("len: %d, af: %p, ts: %d\n", len, af, ot->chunk_ts);
+ ot->streaming = 1;
+ }
+ if (list_empty(&targets))
+ return;
+ if (stream_type) {
+ header_buf = af->get_header_info(&header_len);
+ if (!need_extra_header(af, chunks_sent))
+ header_len = 0;
+ }
+ sendbuf_len = ORTP_AUDIO_HEADER_LEN + header_len + len;
+ sendbuf = para_malloc(sendbuf_len);
+ if (!current_chunk)
+ packet_type = ORTP_BOF;
+ else if (header_len)
+ packet_type = ORTP_HEADER;
+ WRITE_PACKET_TYPE(sendbuf, packet_type);
+ WRITE_CHUNK_TIME(sendbuf, af->chunk_tv.tv_usec);
+ WRITE_STREAM_TYPE(sendbuf, stream_type);
+ WRITE_HEADER_LEN(sendbuf, header_len);
+ if (header_len)
+ memcpy(sendbuf + ORTP_AUDIO_HEADER_LEN, header_buf,
+ header_len);
+ memcpy(sendbuf + ORTP_AUDIO_HEADER_LEN + header_len, buf, len);
+ ortp_send_buf(sendbuf, sendbuf_len, chunks_sent);
+ free(sendbuf);
+}
+
+static int ortp_com_on(struct sender_command_data *scd)
+{
+
+ self->status = SENDER_ON;
+ return 1;
+}
+
+static int ortp_com_off(struct sender_command_data *scd)
+{
+ ortp_shutdown_targets();
+ self->status = SENDER_OFF;
+ return 1;
+}
+
+static int ortp_com_delete(struct sender_command_data *scd)
+{
+ char *a = para_strdup(inet_ntoa(scd->addr));
+ struct ortp_target *ot, *tmp;
+ list_for_each_entry_safe(ot, tmp, &targets, node) {
+ if (scd->port != ot->port)
+ continue;
+ if (strcmp(TARGET_ADDR(ot), a))
+ continue;
+ ortp_delete_target(ot, "com_delete");
+ }
+ return 1;
+}
+
+static void ortp_add_target(int port, struct in_addr *addr)
+{
+ struct ortp_target *ot = para_calloc(sizeof(struct ortp_target));
+ ot->port = port;
+ ot->addr = *addr;
+ PARA_INFO_LOG("adding to target list (%s:%d)\n",
+ TARGET_ADDR(ot), ot->port);
+ list_add(&ot->node, &targets);
+}
+
+static int ortp_com_add(struct sender_command_data *scd)
+{
+ int port = (scd->port > 0)? scd->port : conf.ortp_default_port_arg;
+ ortp_add_target(port, &scd->addr);
+ return 1;
+}
+
+static char *ortp_info(void)
+{
+ struct ortp_target *ot;
+ char *ret, *tgts = NULL;
+
+ list_for_each_entry(ot, &targets, node) {
+ char *tmp = make_message("%s%s:%d ", tgts? tgts : "",
+ TARGET_ADDR(ot), ot->port);
+ free(tgts);
+ tgts = tmp;
+ }
+ ret = make_message(
+ "ortp status: %s\n"
+ "ortp default port: udp %d\n"
+ "ortp targets: %s\n",
+ (self->status == SENDER_ON)? "on" : "off",
+ conf.ortp_default_port_arg,
+ tgts? tgts : "(none)"
+ );
+ free(tgts);
+ return ret;
+}
+
+static void ortp_init_target_list(void)
+{
+ int i;
+
+ INIT_LIST_HEAD(&targets);
+ for (i = 0; i < conf.ortp_target_given; i++) {
+ char *arg = para_strdup(conf.ortp_target_arg[i]);
+ char *p = strchr(arg, ':');
+ int port;
+ struct in_addr addr;
+
+ if (!p)
+ goto err;
+ *p = '\0';
+ if (!inet_aton(arg, &addr))
+ goto err;
+ port = atoi(++p);
+ if (port < 0 || port > 65535)
+ port = conf.ortp_default_port_arg;
+ ortp_add_target(port, &addr);
+ goto success;
+err:
+ PARA_CRIT_LOG("syntax error for ortp_target option "
+ "#%d, ignoring\n", i);
+success:
+ free(arg);
+ continue;
+ }
+}
+
+static void ortp_pre_select(struct audio_format *af, int *max_fileno,
+ fd_set *rfds, fd_set *wfds)
+{
+ return;
+}
+
+static char *ortp_help(void)
+{
+ return make_message(
+ "usage: {on|off}\n"
+ "usage: {add|delete} IP port\n"
+ "example: add 224.0.1.38 1500 (LAN-streaming)\n"
+ );
+}
+
+/**
+ * the init function of para_server's ortp sender
+ *
+ * \param s pointer to the http sender struct
+ *
+ * It initializes all function pointers of \a s and the list of ortp targets.
+ */
+void ortp_send_init(struct sender *s)
+{
+ ortp_init();
+ ortp_set_debug_file("oRTP", NULL);
+ INIT_LIST_HEAD(&targets);
+ s->info = ortp_info;
+ s->help = ortp_help;
+ s->send = ortp_send;
+ s->pre_select = ortp_pre_select;
+ s->post_select = NULL;
+ s->shutdown_clients = ortp_shutdown_targets;
+ s->client_cmds[SENDER_ON] = ortp_com_on;
+ s->client_cmds[SENDER_OFF] = ortp_com_off;
+ s->client_cmds[SENDER_DENY] = NULL;
+ s->client_cmds[SENDER_ALLOW] = NULL;
+ s->client_cmds[SENDER_ADD] = ortp_com_add;
+ s->client_cmds[SENDER_DELETE] = ortp_com_delete;
+ self = s;
+ s->status = SENDER_OFF;
+ ortp_init_target_list();
+ if (!conf.ortp_no_autostart_given)
+ s->status = SENDER_ON;
+ PARA_DEBUG_LOG("%s", "ortp sender init complete\n");
+}
--- /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 para.h global paraslash definitions */
+
+#include "config.h"
+
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <sys/signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h> /* time(), localtime() */
+#include <unistd.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stropts.h>
+#include <poll.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/ipc.h>
+#include <sys/sem.h>
+#include <ctype.h>
+#include <sys/un.h> /* needed by create_pf_socket */
+#include "gcc-compat.h"
+
+/* some internal constants */
+#define STRINGSIZE 4096
+#define MAXLINE 255
+
+
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+#define MAX(a,b) ((a) > (b) ? (a) : (b))
+#define ABS(a) ((a) > 0 ? (a) : -(a))
+
+/* Loglevels */
+#define DEBUG 1
+#define INFO 2
+#define NOTICE 3
+#define WARNING 4
+#define ERROR 5
+#define CRIT 6
+#define EMERG 7
+
+
+#define COMPILE_TIME_LOGLEVEL 0
+#if DEBUG > COMPILE_TIME_LOGLEVEL
+#define PARA_DEBUG_LOG(f,...) para_log(DEBUG, "%s: " f, __FUNCTION__, __VA_ARGS__)
+#else
+#define PARA_DEBUG_LOG(...)
+#endif
+
+#if INFO > COMPILE_TIME_LOGLEVEL
+#define PARA_INFO_LOG(f,...) para_log(INFO, "%s: " f, __FUNCTION__, __VA_ARGS__)
+#else
+#define PARA_INFO_LOG(...)
+#endif
+
+#if NOTICE > COMPILE_TIME_LOGLEVEL
+#define PARA_NOTICE_LOG(f,...) para_log(NOTICE, "%s: " f, __FUNCTION__, __VA_ARGS__)
+#else
+#define PARA_NOTICE_LOG(...)
+#endif
+
+#if WARNING > COMPILE_TIME_LOGLEVEL
+#define PARA_WARNING_LOG(f,...) para_log(WARNING, "%s: " f, __FUNCTION__, __VA_ARGS__)
+#else
+#define PARA_WARNING_LOG(...)
+#endif
+
+#if ERROR > COMPILE_TIME_LOGLEVEL
+#define PARA_ERROR_LOG(f,...) para_log(ERROR, "%s: " f, __FUNCTION__, __VA_ARGS__)
+#else
+#define PARA_ERROR_LOG(...)
+#endif
+
+#if CRIT > COMPILE_TIME_LOGLEVEL
+#define PARA_CRIT_LOG(f,...) para_log(CRIT, "%s: " f, __FUNCTION__, __VA_ARGS__)
+#else
+#define PARA_CRIT_LOG(...)
+#endif
+
+#if EMERG > COMPILE_TIME_LOGLEVEL
+#define PARA_EMERG_LOG(f,...) para_log(EMERG, "%s: " f, __FUNCTION__, __VA_ARGS__)
+#else
+#define PARA_EMERG_LOG(...)
+#endif
+
+#define COPYRIGHT "Copyright (c) 1997-2006 by Andre Noll"
+
+#define LICENSE "This is free software with ABSOLUTELY NO WARRANTY. " \
+ "See COPYING for details."
+
+#define AWAITING_DATA_MSG "\nAwaiting Data."
+#define PROCEED_MSG "\nProceed.\n"
+#define PROCEED_MSG_LEN strlen(PROCEED_MSG)
+#define EOC_MSG "\nEnd of Command."
+#define CHALLENGE_RESPONSE_MSG "challenge_response:"
+
+/* gui_common */
+int para_open_audiod_pipe(char *);
+int read_audiod_pipe(int, void (*)(char *));
+
+/* exec */
+int file_exists(const char *);
+int para_exec(pid_t *, const char *, char *const [], int *);
+int para_exec_cmdline_pid(pid_t *, char *, int *);
+
+/* signal */
+int para_signal_init(void);
+int para_install_sighandler(int);
+void para_reap_children(void);
+pid_t para_reap_child(void);
+int para_next_signal(void);
+
+/* time */
+int tv_diff(const struct timeval *b, const struct timeval *a, struct timeval *diff);
+long unsigned tv2ms(const struct timeval*);
+void d2tv(double, struct timeval*);
+void tv_add(const struct timeval*, const struct timeval *, struct timeval *);
+void tv_scale(const unsigned long, const struct timeval *, struct timeval *);
+void tv_divide(const unsigned long div, const struct timeval *tv,
+ struct timeval *result);
+int tv_convex_combination(const long a, const struct timeval *tv1,
+ const long b, const struct timeval *tv2,
+ struct timeval *result);
+void ms2tv(const long unsigned n, struct timeval *tv);
+
+/* stat */
+enum {
+ SI_STATUS_BAR, SI_STATUS, SI_NUM_PLAYED,
+ SI_MTIME, SI_LENGTH_MIN, SI_LENGTH_SEC,
+ SI_FILE_SIZE, SI_STATUS_FLAGS, SI_FORMAT,
+ SI_SCORE, SI_AUDIO_INFO1, SI_AUDIO_INFO2,
+ SI_AUDIO_INFO3, SI_DBINFO1, SI_DBINFO2,
+ SI_DBINFO3, SI_DECODER_FLAGS, SI_AUDIOD_STATUS,
+ SI_PLAY_TIME, SI_UPTIME, SI_OFFSET,
+ SI_LENGTH, SI_STREAM_START, SI_CURRENT_TIME,
+ SI_AUDIOD_UPTIME, SI_DBTOOL,
+};
+#define NUM_STAT_ITEMS (SI_DBTOOL + 1)
+int stat_line_valid(const char *);
+void stat_client_write(char *msg);
+int stat_client_add(int);
+void dump_empty_status(void);
+unsigned for_each_line(char *, int, void (*)(char *), int);
+
+struct stat_item_data {
+ char *prefix, *postfix;
+ unsigned x, y, len;
+ int fg, bg, align;
+};
+
+/* gui_theme */
+struct gui_theme {
+ char *name;
+ char *author;
+ int sb_fg, sb_bg;
+ int cmd_fg, cmd_bg;
+ int output_fg, output_bg;
+ int msg_fg, msg_bg;
+ int err_msg_fg, err_msg_bg;
+ int welcome_fg, welcome_bg;
+ int sep_fg, sep_bg;
+ char *sep_str;
+ int default_fg, default_bg;
+
+ int top_lines_default, top_lines_min;
+ int lines_min, cols_min;
+ struct stat_item_data data[NUM_STAT_ITEMS];
+};
+
+void init_theme(int i, struct gui_theme *);
+void next_theme(struct gui_theme *);
+void prev_theme(struct gui_theme *);
+#define LEFT 1
+#define RIGHT 2
+#define CENTER 3
+
+
+__printf_2_3 void para_log(int, char*, ...);
+
+/* taken from printf man page */
+#define PARA_VSPRINTF(fmt, p) \
+{ \
+ int n, size = 100; \
+ p = para_malloc(size); \
+ while (1) { \
+ va_list ap; \
+ /* Try to print in the allocated space. */ \
+ va_start(ap, fmt); \
+ n = vsnprintf(p, size, fmt, ap); \
+ va_end(ap); \
+ /* If that worked, return the string. */ \
+ if (n > -1 && n < size) \
+ break; \
+ /* Else try again with more space. */ \
+ if (n > -1) /* glibc 2.1 */ \
+ size = n + 1; /* precisely what is needed */ \
+ else /* glibc 2.0 */ \
+ size *= 2; /* twice the old size */ \
+ p = para_realloc(p, size); \
+ } \
+}
+
+
+
--- /dev/null
+Jul 28 08:05:15 1: (29586) afs_send_chunk: Song finished (flags:8)
+Jul 28 08:05:15 1: (29586) get_song: Getting next song
+Jul 28 08:05:16 1: (29586) get_song: Opening /home/mp3/checked/cd_37/Rodgau_Monotones__Is_Mir_Egal.mp3
+Jul 28 08:05:16 1: (29586) update_mmd: initialising mmd struct
+Jul 28 08:05:16 4: (29586) update_mmd: Next song: /home/mp3/checked/cd_37/Rodgau_Monotones__Is_Mir_Egal.mp3
+Jul 28 08:05:16 3: (29586) update_mmd: size: 4664869, mtime: Tue Jul 13 18:50:40 2004
+Jul 28 08:05:16 3: (29586) update_mmd: current stream: wake, num_played: 35
+Jul 28 08:05:16 3: (29586) update_mmd: length: 3:53, 160 KBit/s, 44100 KHz (joint stereo)
+Jul 28 08:05:16 1: (29586) update_mmd: mmd.length=233, mmd.size=4664869
+Jul 28 08:05:16 1: (29586) update_dbinfo: getting dbtool info /home/mp3/checked/cd_37/Rodgau_Monotones__Is_Mir_Egal.mp3
+Jul 28 08:05:16 3: (29586) update_dbinfo: dir: /home/mp3/checked/cd_37
+Jul 28 08:05:16 3: (29586) update_dbinfo: last played: 4 day(s) ago. Numplayed: 5, Pic_Id: 314
+Jul 28 08:05:16 3: (29586) update_dbinfo: attributes: fast fun deutsch rock
+Jul 28 08:05:16 1: (29586) update_dbinfo: Read 116 byte of info and put 140 byte into mmd->dbinfo
+Jul 28 08:05:16 1: (29586) update_mmd: calling dbtool to update song
+Jul 28 08:05:16 1: (29586) main: Caught signal 17
+Jul 28 08:05:16 1: (29586) handle_sigchld: child 3956 exited. Exit status: 0
+Jul 28 08:05:16 1: (29586) handle_sigchld: child 3959 exited. Exit status: 0
+Jul 28 08:05:16 1: (29586) main: Caught signal 17
+Jul 28 08:05:16 1: (29586) main: Caught signal 17
+Jul 28 08:05:16 1: (29586) handle_sigchld: child 3961 exited. Exit status: 0
+Jul 28 08:05:16 4: (29586) main: Got connection from 192.168.0.7
+Jul 28 08:05:16 1: (3963) handle_connect: Received auth request for user maan
+Jul 28 08:05:16 1: (3963) open_user_list: Opening config file /home/maan/.paraslash/server.users
+Jul 28 08:05:16 1: (3963) get_user: found entry for maan
+Jul 28 08:05:16 1: (3963) get_user: found 4 perm entries
+Jul 28 08:05:16 1: (3963) get_user: tmp[3]=AFS_WRITE
+Jul 28 08:05:16 1: (3963) get_user: tmp[2]=AFS_READ
+Jul 28 08:05:16 1: (3963) get_user: tmp[1]=DB_WRITE
+Jul 28 08:05:16 1: (3963) get_user: tmp[0]=DB_READ
+Jul 28 08:05:16 1: (3964) encrypt: dup2ing stdin
+Jul 28 08:05:16 1: (3964) encrypt: dup2ing stdout
+Jul 28 08:05:16 1: (3963) encrypt: Writing challenge to pipe (11 bytes)
+Jul 28 08:05:16 1: (3963) encrypt: Reading line from pipe
+Jul 28 08:05:16 1: (3963) encrypt: Read 64 byte from pipe
+Jul 28 08:05:16 1: (3963) handle_connect: Sending 64 byte challenge for maan
+Jul 28 08:05:16 3: (3963) handle_connect: Good auth for maan (1894092672)
+Jul 28 08:05:16 1: (3963) parse_cmd: Found command pic. Perms: 1
+Jul 28 08:05:16 1: (3963) check_perms: check_perms
+Jul 28 08:05:16 4: (3963) handle_connect: executing command handler for command "pic"
+Jul 28 08:05:16 1: (29586) main: Caught signal 17
+Jul 28 08:05:16 1: (29586) handle_sigchld: child 3963 exited. Exit status: 0
+
--- /dev/null
+Mar 23 20:47:26 3: (19185) do_inits: welcome to para_server 0.1.6-dev (Wed Mar 23 20:39:23 MET 2005)
+Mar 23 20:47:26 3: (19185)
+Mar 23 20:47:26 1: (19185) do_inits: using loglevel 1
+Mar 23 20:47:26 4: (19185) init_dbtool: initializing database tool
+Mar 23 20:47:26 1: (19185) init_mysql_server: connecting: maan@localhost:3306
+Mar 23 20:47:26 3: (19185) init_mysql_server: successfully connected to mysql server
+Mar 23 20:47:26 3: (19185) init_dbtool: initialized mysql
+Mar 23 20:47:26 4: (19185) do_inits: initializing audio file sender
+Mar 23 20:47:26 3: (19185) afs_init: checking audio formats
+Mar 23 20:47:26 1: (19185) afs_init: supported audio format: mp3
+Mar 23 20:47:26 1: (19185) setup_stream_command: 1 stream write command given, checking for mp3
+Mar 23 20:47:26 3: (19185) setup_stream_command: using "poc-fec -q -" for files of type mp3 (default)
+Mar 23 20:47:26 1: (19185) afs_init: supported audio format: ogg
+Mar 23 20:47:26 1: (19185) setup_stream_command: 1 stream write command given, checking for ogg
+Mar 23 20:47:26 3: (19185) setup_stream_command: using "/home/maan/para/para_ovsend -r /home/maan/.paraslash/ogg_fifo.p133" for files of type ogg
+Mar 23 20:47:26 1: (19185) afs_init: supported audio formats: mp3 ogg
+Mar 23 20:47:26 4: (19185) setup_signal_handling: setting up signal handlers
+Mar 23 20:47:26 1: (19185) para_install_sighandler: catching signal 2
+Mar 23 20:47:26 1: (19185) para_install_sighandler: catching signal 15
+Mar 23 20:47:26 1: (19185) para_install_sighandler: catching signal 1
+Mar 23 20:47:26 1: (19185) para_install_sighandler: catching signal 17
+Mar 23 20:47:26 4: (19185) do_inits: initializing networking
+Mar 23 20:47:26 3: (19185) init_network: init network socket
+Mar 23 20:47:26 4: (19185) do_inits: init complete
+Mar 23 20:47:26 1: (19185) afs_mainloop: not playing
+Mar 23 20:47:29 4: (19185) main: got connection from 192.168.0.8, forking
+Mar 23 20:47:29 1: (19185) afs_mainloop: not playing
+Mar 23 20:47:29 1: (19187) handle_connect: Received auth request for user maan
+Mar 23 20:47:29 1: (19187) open_user_list: Opening user list /home/maan/.paraslash/server.users
+Mar 23 20:47:29 1: (19187) get_user: found entry for maan
+Mar 23 20:47:29 1: (19187) get_user: found 4 perm entries
+Mar 23 20:47:29 3: (19187) encrypt: writing challenge to pipe (11 bytes)
+Mar 23 20:47:29 1: (19187) encrypt: reading response
+Mar 23 20:47:29 1: (19187) encrypt: read 64 byte from pipe
+Mar 23 20:47:29 1: (19187) handle_connect: sending 64 byte challenge
+Mar 23 20:47:29 3: (19187) handle_connect: good auth for maan (3859497713)
+Mar 23 20:47:29 1: (19187) parse_cmd: found command play.
+Mar 23 20:47:29 1: (19187) check_perms: checking permissions
+Mar 23 20:47:29 4: (19187) handle_connect: executing command handler for command "play"
+Mar 23 20:47:29 3: (19187) handle_connect: command handler returned success
+Mar 23 20:47:29 1: (19185) afs_mainloop: no audio file
+Mar 23 20:47:29 1: (19185) real_query: select def from streams where name = 'current_stream'
+Mar 23 20:47:29 1: (19185) real_query: select def from streams where name='gulp'
+Mar 23 20:47:29 1: (19185) real_query: select concat(dir.dir, '/', dir.name) from data,dir where dir.name = data.name and ( (dir.name like '%G.U.L.P%')) order by -((FLOOR((UNIX_TIMESTAMP(now())-UNIX_TIMESTAMP(data.Lastplayed))/60) / 1440 - sqrt(data.Numplayed)))
+Mar 23 20:47:29 3: (19185) get_song: trying /home/mp3/checked/cd_20/The_G.U.L.P.__U-Bahn.mp3
+Mar 23 20:47:29 1: (19185) guess_audio_format: might be mp3
+Mar 23 20:47:29 3: (19185) get_file_info: valid mp3 file
+Mar 23 20:47:29 4: (19185) update_mmd: next audio file: /home/mp3/checked/cd_20/The_G.U.L.P.__U-Bahn.mp3
+Mar 23 20:47:29 1: (19185) real_query: select def from streams where name = 'current_stream'
+Mar 23 20:47:29 1: (19185) real_query: select concat('lastplayed: ', (to_days(now()) - to_days(lastplayed)),' day(s). numplayed: ', numplayed, ', pic: ', pic_id) from data where name = 'The_G.U.L.P.__U-Bahn.mp3'
+Mar 23 20:47:29 1: (19185) real_query: desc data
+Mar 23 20:47:29 1: (19185) real_query: select * from data where name='The_G.U.L.P.__U-Bahn.mp3'
+Mar 23 20:47:29 1: (19185) real_query: select dir from dir where name = 'The_G.U.L.P.__U-Bahn.mp3'
+Mar 23 20:47:29 1: (19185) real_query: select def from streams where name='gulp'
+Mar 23 20:47:29 1: (19185) real_query: select (FLOOR((UNIX_TIMESTAMP(now())-UNIX_TIMESTAMP(data.Lastplayed))/60) / 1440 - sqrt(data.Numplayed)) from data where name = 'The_G.U.L.P.__U-Bahn.mp3'
+Mar 23 20:47:29 1: (19185) update_mmd: updating shared memory area
+Mar 23 20:47:29 1: (19185) real_query: update data set lastplayed = now() where name='The_G.U.L.P.__U-Bahn.mp3'
+Mar 23 20:47:29 1: (19185) real_query: select numplayed from data where name = 'The_G.U.L.P.__U-Bahn.mp3'
+Mar 23 20:47:29 1: (19185) real_query: update data set numplayed=38 where name='The_G.U.L.P.__U-Bahn.mp3'
+Mar 23 20:47:29 1: (19185) get_song: success
+Mar 23 20:47:29 1: (19185) afs_mainloop: open stream
+Mar 23 20:47:29 3: (19185) afs_open_stream_writer: opening mp3 stream_writer (poc-fec -q -)
+Mar 23 20:47:29 1: (19185) afs_open_stream_writer: mp3 stream writer pid: 19189, fd: 10
+Mar 23 20:47:29 1: (19185) para_next_signal: next signal: 17
+Mar 23 20:47:29 1: (19185) para_reap_child: child 19187 exited. Exit status: 0
--- /dev/null
+Jan 22 03:17:31 meins 2 log_welcome: welcome to para_audiod cvs (Sun Jan 22 02:51:13 MET 2006)
+Jan 22 03:17:31 meins 2 init_stream_io: initializing http receiver
+Jan 22 03:17:31 meins 2 init_stream_io: initializing ortp receiver
+Jan 22 03:17:31 meins 2 init_stream_io: initializing wav filter
+Jan 22 03:17:31 meins 2 init_stream_io: initializing compress filter
+Jan 22 03:17:31 meins 2 init_stream_io: initializing mp3dec filter
+Jan 22 03:17:31 meins 2 init_stream_io: initializing oggdec filter
+Jan 22 03:17:31 meins 2 add_filter: mp3 filter 1: mp3dec
+Jan 22 03:17:31 meins 2 add_filter: mp3 filter 2: compress
+Jan 22 03:17:31 meins 2 add_filter: mp3 filter 3: wav
+Jan 22 03:17:31 meins 2 add_filter: ogg filter 1: oggdec
+Jan 22 03:17:31 meins 2 add_filter: ogg filter 2: compress
+Jan 22 03:17:31 meins 2 add_filter: ogg filter 3: wav
+Jan 22 03:17:31 meins 2 clear_slot: clearing slot 0
+Jan 22 03:17:31 meins 2 clear_slot: clearing slot 1
+Jan 22 03:17:31 meins 2 clear_slot: clearing slot 2
+Jan 22 03:17:31 meins 2 clear_slot: clearing slot 3
+Jan 22 03:17:31 meins 2 clear_slot: clearing slot 4
+Jan 22 03:17:31 meins 2 init_grabbing: grab init
+Jan 22 03:17:31 meins 2 setup_signal_handling: signal pipe: fd 4
+Jan 22 03:17:31 meins 2 daemon_init: daemonizing
+Jan 22 03:17:31 meins 3 open_stat_pipe: stat pipe opened, fd 6
+Jan 22 03:17:31 meins 3 audiod_get_socket: connecting to local socket /var/paraslash/audiod_socket.meins
+Jan 22 03:17:32 meins 2 handle_connect: pid: 8807, uid: 409, gid: 100, ret: 254, buf: stat
+Jan 22 03:17:32 meins 2 handle_connect: argv[0]: stat
+Jan 22 03:17:32 meins 2 stat_client_add: adding client on fd 8
+Jan 22 03:17:32 meins 2 dump_stat_client_list: stat client on fd 8
+Jan 22 03:17:32 meins 2 handle_connect: pid: 8806, uid: 409, gid: 100, ret: 254, buf: stat
+Jan 22 03:17:32 meins 2 handle_connect: argv[0]: stat
+Jan 22 03:17:32 meins 2 stat_client_add: adding client on fd 9
+Jan 22 03:17:32 meins 2 dump_stat_client_list: stat client on fd 9
+Jan 22 03:17:32 meins 2 dump_stat_client_list: stat client on fd 8
+Jan 22 03:17:46 meins 2 compute_time_diff: time diff (cur/avg): 0:1/0:0
--- /dev/null
+Jan 22 03:08:46 2: (27321) log_welcome: welcome to para_server cvs (Sun Jan 22 02:51:13 MET 2006)
+Jan 22 03:08:46 1: (27321) log_welcome: using loglevel 1
+Jan 22 03:08:46 2: (27321) daemon_init: daemonizing
+Jan 22 03:08:46 3: (27322) init_dbtool: initializing mysql database tool
+Jan 22 03:08:46 1: (27322) init_mysql_server: connecting: maan@localhost:3306
+Jan 22 03:08:46 2: (27322) init_mysql_server: successfully connected to mysql server
+Jan 22 03:08:46 2: (27322) init_dbtool: initialized mysql
+Jan 22 03:08:46 3: (27322) do_inits: initializing audio file sender
+Jan 22 03:08:46 1: (27322) afs_init: supported audio formats: mp3 ogg
+Jan 22 03:08:46 3: (27322) afs_init: initializing mp3 handler
+Jan 22 03:08:46 3: (27322) afs_init: initializing ogg handler
+Jan 22 03:08:46 2: (27322) afs_init: announce timeval: 0:300000
+Jan 22 03:08:46 3: (27322) afs_init: initializing http sender
+Jan 22 03:08:46 2: (27322) add_perm_list_entry: adding 192.168.0.1/32 to access list
+Jan 22 03:08:46 2: (27322) add_perm_list_entry: adding 192.168.0.4/32 to access list
+Jan 22 03:08:46 2: (27322) add_perm_list_entry: adding 192.168.0.8/32 to access list
+Jan 22 03:08:46 2: (27322) init_tcp_socket: listening on port 8000, fd 5
+Jan 22 03:08:46 1: (27322) para_http_init: http sender init complete
+Jan 22 03:08:46 3: (27322) afs_init: initializing ortp sender
+Jan 22 03:08:46 2: (27322) ortp_add_target: adding to target list (224.0.1.38:1500)
+Jan 22 03:08:46 1: (27322) para_ortp_init: ortp sender init complete
+Jan 22 03:08:46 3: (27322) setup_signal_handling: setting up signal handlers
+Jan 22 03:08:46 1: (27322) para_install_sighandler: catching signal 2
+Jan 22 03:08:46 1: (27322) para_install_sighandler: catching signal 15
+Jan 22 03:08:46 1: (27322) para_install_sighandler: catching signal 1
+Jan 22 03:08:46 1: (27322) para_install_sighandler: catching signal 17
+Jan 22 03:08:46 3: (27322) do_inits: initializing tcp command socket
+Jan 22 03:08:46 2: (27322) init_tcp_socket: listening on port 2990, fd 8
+Jan 22 03:08:46 3: (27322) do_inits: init complete
+Jan 22 03:08:46 1: (27322) status_refresh: 0 events, forcing status update, af = -1
+Jan 22 03:08:47 2: (27322) main: got connection from 192.168.0.8, forking
+Jan 22 03:08:47 1: (27328) close_listed_fds: closing fd 5
+Jan 22 03:08:47 1: (27328) handle_connect: received rc4 request for user maan
+Jan 22 03:08:47 1: (27328) open_user_list: opening user list /home/maan/.paraslash/server.users
+Jan 22 03:08:47 1: (27328) get_user: found entry for maan
+Jan 22 03:08:47 1: (27328) get_user: found 4 perm entries
+Jan 22 03:08:47 1: (27328) handle_connect: sending 64 byte challenge
+Jan 22 03:08:47 2: (27328) handle_connect: good auth for maan (1359837743)
+Jan 22 03:08:47 1: (27328) init_rc4_keys: rc4 keys initialized (198:20)
+Jan 22 03:08:47 2: (27328) handle_connect: rc4 encrytion activated
+Jan 22 03:08:47 1: (27328) parse_cmd: found command stat
+Jan 22 03:08:47 1: (27328) check_perms: checking permissions
+Jan 22 03:08:47 3: (27328) handle_connect: calling com_stat() for maan@192.168.0.8
+Jan 22 03:08:47 2: (27322) main: got connection from 192.168.0.4, forking
+Jan 22 03:08:47 1: (27329) close_listed_fds: closing fd 5
+Jan 22 03:08:47 1: (27329) handle_connect: received rc4 request for user maan
+Jan 22 03:08:47 1: (27329) open_user_list: opening user list /home/maan/.paraslash/server.users
+Jan 22 03:08:47 1: (27329) get_user: found entry for maan
+Jan 22 03:08:47 1: (27329) get_user: found 4 perm entries
+Jan 22 03:08:47 1: (27329) handle_connect: sending 64 byte challenge
+Jan 22 03:08:47 2: (27329) handle_connect: good auth for maan (1661875263)
+Jan 22 03:08:47 1: (27329) init_rc4_keys: rc4 keys initialized (246:109)
+Jan 22 03:08:47 2: (27329) handle_connect: rc4 encrytion activated
+Jan 22 03:08:47 1: (27329) parse_cmd: found command stat
+Jan 22 03:08:47 1: (27329) check_perms: checking permissions
+Jan 22 03:08:47 3: (27329) handle_connect: calling com_stat() for maan@192.168.0.4
--- /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.
+ */
+
+/*
+ * Based in parts on aplay.c from the alsa-utils-1.0.8 package,
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>, which is
+ * based on the vplay program by Michael Beck.
+ */
+
+#define BUFFER_SIZE 1000 * 1000
+#define WAV_HEADER_LEN 44
+#include <sys/time.h> /* gettimeofday */
+#include "para.h"
+#include "play.cmdline.h"
+#include <alsa/asoundlib.h>
+
+enum { E_BROKEN_CONF, /* Broken configuration for this PCM */
+ E_ACCESS_TYPE, /* Access type not available */
+ E_SAMPLE_FORMAT, /* Sample format not available */
+ E_CHANNEL_COUNT, /* Channels count not available */
+ E_HW_PARAMS, /* Unable to install hw params */
+ E_SW_PARAMS, /* Unable to install sw params */
+ E_BAD_PERIOD, /* Can't use period equal to buffer size */
+ E_GET_XFER, /* Unable to obtain xfer align */
+ E_SET_XFER, /* snd_pcm_sw_params_set_xfer_align failed */
+ E_MEM, /* not enough memory */
+ E_READ, /* read error */
+ E_WRITE, /* write error */
+ E_PIPE, /* write to pipe with other side closed */
+ E_PCM_OPEN, /* unable to open pcm */
+ E_SND_PCM_INFO, /* pcm info error */
+ E_GET_BUFFER_TIME, /* snd_pcm_hw_params_get_buffer_time_max failed */
+ E_SET_BUFFER_TIME, /* snd_pcm_hw_params_set_buffer_time_near failed */
+ E_SET_RATE, /* snd_pcm_hw_params_set_rate_near failed */
+ E_START_THRESHOLD, /* snd_pcm_sw_params_set_start_threshold failed */
+ E_STOP_THRESHOLD, /* snd_pcm_sw_params_set_stop_threshold failed */
+ E_LOG, /* snd_output_stdio_attach failed */
+ E_SYNTAX /* could not parse start_time option */
+};
+
+#define FORMAT SND_PCM_FORMAT_S16_LE
+
+#define EXIT(EXP) \
+do { if (EXP) \
+ fprintf (stderr, "error: " #EXP "\n"); exit(EXP);} \
+while (0)
+
+static snd_pcm_t *handle;
+static unsigned char *audiobuf;
+static snd_pcm_uframes_t chunk_size;
+static size_t bytes_per_frame;
+static struct timeval *start_time;
+static struct gengetopt_args_info conf;
+
+/*
+ * read_wav_header - read WAV_HEADER_LEN bytes from stdin to audio buffer
+ *
+ * Exit on errors and on eof before WAV_HEADER_LEN could be read.
+ */
+static void read_wav_header(void)
+{
+ ssize_t ret, count = 0;
+
+ while (count < WAV_HEADER_LEN) {
+ ret = read(STDIN_FILENO, audiobuf + count, WAV_HEADER_LEN - count);
+ if (ret <= 0)
+ EXIT(E_READ);
+ count += ret;
+ }
+}
+
+/*
+ * set_alsa_params - Prepare the PCM handle for writing
+ *
+ * Install PCM software and hardware configuration. Exit on errors.
+ */
+static void set_alsa_params(void)
+{
+ snd_pcm_hw_params_t *hwparams;
+ snd_pcm_sw_params_t *swparams;
+ snd_pcm_uframes_t buffer_size, xfer_align, start_threshold,
+ stop_threshold;
+ unsigned buffer_time = 0;
+ int err;
+
+ snd_pcm_hw_params_alloca(&hwparams);
+ snd_pcm_sw_params_alloca(&swparams);
+ if (snd_pcm_hw_params_any(handle, hwparams) < 0)
+ EXIT(E_BROKEN_CONF);
+ if (snd_pcm_hw_params_set_access(handle, hwparams,
+ SND_PCM_ACCESS_RW_INTERLEAVED) < 0)
+ EXIT(E_ACCESS_TYPE);
+ if (snd_pcm_hw_params_set_format(handle, hwparams, FORMAT) < 0)
+ EXIT(E_SAMPLE_FORMAT);
+ if (snd_pcm_hw_params_set_channels(handle, hwparams, conf.channels_arg) < 0)
+ EXIT(E_CHANNEL_COUNT);
+ if (snd_pcm_hw_params_set_rate_near(handle, hwparams, (unsigned int*) &conf.sample_rate_arg, 0) < 0)
+ EXIT(E_SET_RATE);
+ err = snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time, 0);
+ if (err < 0 || !buffer_time)
+ EXIT(E_GET_BUFFER_TIME);
+ if (buffer_time > 500000)
+ buffer_time = 500000;
+ if (snd_pcm_hw_params_set_buffer_time_near(handle, hwparams,
+ &buffer_time, 0) < 0)
+ EXIT(E_SET_BUFFER_TIME);
+ if (snd_pcm_hw_params(handle, hwparams) < 0)
+ EXIT(E_HW_PARAMS);
+ snd_pcm_hw_params_get_period_size(hwparams, &chunk_size, 0);
+ snd_pcm_hw_params_get_buffer_size(hwparams, &buffer_size);
+ if (chunk_size == buffer_size)
+ EXIT(E_BAD_PERIOD);
+ snd_pcm_sw_params_current(handle, swparams);
+ err = snd_pcm_sw_params_get_xfer_align(swparams, &xfer_align);
+ if (err < 0 || !xfer_align)
+ EXIT(E_GET_XFER);
+ snd_pcm_sw_params_set_sleep_min(handle, swparams, 0);
+ snd_pcm_sw_params_set_avail_min(handle, swparams, chunk_size);
+ /* round to closest transfer boundary */
+ start_threshold = (buffer_size / xfer_align) * xfer_align;
+ if (start_threshold < 1)
+ start_threshold = 1;
+ if (snd_pcm_sw_params_set_start_threshold(handle, swparams,
+ start_threshold) < 0)
+ EXIT(E_START_THRESHOLD);
+ stop_threshold = buffer_size;
+ if (snd_pcm_sw_params_set_stop_threshold(handle, swparams,
+ stop_threshold) < 0)
+ EXIT(E_STOP_THRESHOLD);
+ if (snd_pcm_sw_params_set_xfer_align(handle, swparams, xfer_align) < 0)
+ EXIT(E_SET_XFER);
+ if (snd_pcm_sw_params(handle, swparams) < 0)
+ EXIT(E_SW_PARAMS);
+ bytes_per_frame = snd_pcm_format_physical_width(FORMAT) * conf.channels_arg / 8;
+}
+
+/*
+ * pcm_write - push out pcm frames
+ * @data: pointer do data to be written
+ * @count: number of frames
+ *
+ * Return value: Number of bytes written. Exit on errors.
+ */
+static snd_pcm_sframes_t pcm_write(u_char *data, size_t count)
+{
+ snd_pcm_sframes_t r, result = 0;
+#if 0
+ if (count < chunk_size) {
+ snd_pcm_format_set_silence(FORMAT, data
+ + count * bytes_per_frame,
+ (chunk_size - count) * conf.channels_arg);
+ count = chunk_size;
+ }
+#endif
+ while (count > 0) {
+ /* write interleaved frames */
+ r = snd_pcm_writei(handle, data, count);
+ if (r == -EAGAIN || (r >= 0 && r < count))
+ snd_pcm_wait(handle, 1);
+ else if (r == -EPIPE)
+ snd_pcm_prepare(handle);
+ else if (r < 0)
+ EXIT(E_WRITE);
+ if (r > 0) {
+ result += r;
+ count -= r;
+ data += r * bytes_per_frame;
+ }
+ }
+ return result;
+}
+
+/*
+ * start_time_in_future - check if current time is later than start_time
+ * @diff: pointer to write remaining time to
+ *
+ * If start_time was not given, or current time is later than given
+ * start_time, return 0. Otherwise, return 1 and write the time
+ * difference between current time and start_time to diff. diff may be
+ * NULL.
+ *
+ */
+static int start_time_in_future(struct timeval *diff)
+{
+ struct timeval now;
+
+ if (!conf.start_time_given)
+ return 0;
+ gettimeofday(&now, NULL);
+ return tv_diff(start_time, &now, diff) > 0? 1 : 0;
+}
+
+/*
+ * do_initial_delay - sleep until time given at command line
+ *
+ * This is called if the initial buffer is filled. It returns
+ * immediately if no start_time was given at the command line
+ * or if the given start time is in the past.
+ *
+ */
+static void do_initial_delay(void)
+{
+ struct timeval diff;
+ int ret;
+
+ fprintf(stderr, "initial delay\n");
+ if (!conf.start_time_given)
+ return;
+again:
+ if (!start_time_in_future(&diff))
+ return;
+ ret = select(1, NULL, NULL, NULL, &diff);
+ if (ret < 0 && errno == EINTR)
+ goto again;
+}
+
+
+/*
+ * play_pcm - play raw pcm data
+ * @l: number of bytes already loaded
+ *
+ * If start_time was given, prebuffer data until buffer is full or
+ * start_time is reached. In any case, do not start playing before
+ * start_time.
+ */
+static void play_pcm(size_t l)
+{
+ ssize_t r, w;
+ unsigned long written = 0;
+ size_t chunk_bytes;
+
+ set_alsa_params();
+ chunk_bytes = chunk_size * bytes_per_frame;
+ audiobuf = realloc(audiobuf, BUFFER_SIZE);
+// fprintf(stderr, "loaded: %d, chunk_bytes: %d\n", l, chunk_bytes);
+ if (!audiobuf)
+ EXIT(E_MEM);
+ for (;;) {
+ for (;;) {
+ if (l >= chunk_bytes) {
+ if (written)
+ break;
+ if (!start_time)
+ break;
+ if (!start_time_in_future(NULL))
+ break;
+ if (l > BUFFER_SIZE) {
+ do_initial_delay();
+ break;
+ }
+ }
+// fprintf(stderr, "l = %d, chunk_Bytes = %d\n", l, chunk_bytes);
+ r = read(STDIN_FILENO, audiobuf + l, BUFFER_SIZE -l);
+ if (r < 0)
+ EXIT(E_READ);
+ l += r;
+// fprintf(stderr, "loaded: %d, r= %d\n", l, r);
+ if (!r)
+ goto out;;
+ }
+ w = MIN(chunk_bytes, l);
+// fprintf(stderr, "play: writing %d\n", w);
+ r = (ssize_t) pcm_write(audiobuf, w / bytes_per_frame) * bytes_per_frame;
+// fprintf(stderr, "wrote %d\n", r);
+ if (r < 0)
+ EXIT(E_WRITE);
+ written += r;
+ l -= r;
+ if (l)
+ memmove(audiobuf, audiobuf + r, l);
+// fprintf(stderr, "written %lu, loaded : %d\n", written, l);
+ }
+out:
+ snd_pcm_drain(handle);
+}
+
+/*
+ * check_wave - test if audio buffer contains a valid wave header
+ *
+ * If not, return 0, otherwise, store number of channels and sample rate
+ * in struct conf and return WAV_HEADER_LEN.
+ */
+static size_t check_wave(void)
+{
+ unsigned char *a = audiobuf;
+ if (a[0] != 'R' || a[1] != 'I' || a[2] != 'F' || a[3] != 'F')
+ return WAV_HEADER_LEN;
+ conf.channels_arg = (unsigned) a[22];
+ conf.sample_rate_arg = a[24] + (a[25] << 8) + (a[26] << 16) + (a[27] << 24);
+// fprintf(stderr, "channels: %d, rate: %d\n", conf.channels_arg,
+// conf.sample_rate_arg);
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ snd_pcm_info_t *info;
+ snd_output_t *log;
+ struct timeval tv;
+ int err;
+
+ cmdline_parser(argc, argv, &conf);
+ if (conf.start_time_given) {
+ if (sscanf(conf.start_time_arg, "%lu:%lu",
+ &tv.tv_sec, &tv.tv_usec) != 2)
+ EXIT(E_SYNTAX);
+ start_time = &tv;
+ }
+// fprintf(stderr, "argc=%d, argv[1]=%s\n",argc, argv[1]);
+ snd_pcm_info_alloca(&info);
+ if (snd_output_stdio_attach(&log, stderr, 0) < 0)
+ EXIT(E_LOG);
+ err = snd_pcm_open(&handle, "plug:swmix",
+ SND_PCM_STREAM_PLAYBACK, 0);
+ if (err < 0)
+ EXIT(E_PCM_OPEN);
+ if ((err = snd_pcm_info(handle, info)) < 0)
+ EXIT(E_SND_PCM_INFO);
+ audiobuf = malloc(WAV_HEADER_LEN);
+ read_wav_header();
+ play_pcm(check_wave());
+ snd_pcm_close(handle);
+ free(audiobuf);
+ snd_output_close(log);
+ snd_config_update_free_global();
+ return EXIT_SUCCESS;
+}
--- /dev/null
+section "general options"
+option "start_time" t "start playback at given time which must be in a:b format where a denotes seconds and b denotes microseconds since the epoch" string typestr="timeval" no
+option "device" d "set PCM device" string typestr="device" default="plug:swmix" no
+option "channels" c "number of channels (only neccessary for raw audio)" int typestr="num" default="2" no
+option "sample_rate" s "force given sample rate (only neccessary for raw audio)" int typestr="num" default="44100" no
--- /dev/null
+#define RC4_KEY_LEN 16
--- /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.
+ */
+#include "gcc-compat.h"
+#include "para.h"
+
+#include "recv.h"
+#include "recv.cmdline.h"
+#include "error.h"
+
+struct gengetopt_args_info conf;
+
+INIT_RECV_ERRLISTS;
+
+__printf_2_3 void para_log(int ll, char* fmt,...)
+{
+ va_list argp;
+
+ /* ignore log message if loglevel is not high enough */
+ if (ll < conf.loglevel_arg)
+ return;
+ va_start(argp, fmt);
+ vfprintf(stderr, fmt, argp);
+ va_end(argp);
+}
+
+static void *parse_config(int argc, char *argv[], int *receiver_num)
+{
+ int i;
+
+ if (cmdline_parser(argc, argv, &conf))
+ return NULL;
+ if (conf.list_receivers_given) {
+ printf("available receivers: ");
+ for (i = 0; receivers[i].name; i++)
+ printf("%s%s", i? " " : "", receivers[i].name);
+ printf("\nTry para_recv -r<receivername>:-h for help on <receivername>\n");
+ exit(EXIT_SUCCESS);
+ }
+ return check_receiver_arg(conf.receiver_arg, receiver_num);
+}
+
+int main(int argc, char *argv[])
+{
+ int ret, eof = 0, max, r_opened = 0, receiver_num;
+ struct timeval timeout;
+ struct receiver *r = NULL;
+ fd_set rfds, wfds;
+ struct receiver_node rn;
+
+ memset(&rn, 0, sizeof(struct receiver_node));
+ for (ret = 0; receivers[ret].name; ret++)
+ receivers[ret].init(&receivers[ret]);
+ ret = -E_RECV_SYNTAX;
+ rn.conf = parse_config(argc, argv, &receiver_num);
+ if (!rn.conf) {
+ PARA_EMERG_LOG("%s", "parse failed\n");
+ goto out;
+ }
+ r = &receivers[receiver_num];
+ rn.receiver = r;
+ ret = r->open(&rn);
+ if (ret < 0)
+ goto out;
+ r_opened = 1;
+recv:
+ FD_ZERO(&rfds);
+ FD_ZERO(&wfds);
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 1000 * 1000;
+ max = -1;
+ ret = r->pre_select(&rn, &rfds, &wfds, &timeout);
+ max = MAX(max, ret);
+
+ PARA_DEBUG_LOG("timeout: %lums\n", tv2ms(&timeout));
+ ret = select(max + 1, &rfds, &wfds, NULL, &timeout);
+ if (ret < 0) {
+ if (errno == EINTR || errno == EAGAIN)
+ goto recv;
+ ret = -E_RECV_SELECT;
+ goto out;
+ }
+ ret = r->post_select(&rn, ret, &rfds, &wfds);
+ if (ret < 0)
+ goto out;
+ if (!ret)
+ eof = 1;
+ if (!rn.loaded) {
+ if (eof)
+ goto out;
+ goto recv;
+ }
+ ret = write(STDOUT_FILENO, rn.buf, rn.loaded);
+ PARA_DEBUG_LOG("wrote %d/%d\n", ret, rn.loaded);
+ if (ret < 0) {
+ ret = -E_WRITE_STDOUT;
+ goto out;
+ }
+ if (ret != rn.loaded) {
+ PARA_INFO_LOG("short write %d/%d\n", ret, rn.loaded);
+ memmove(rn.buf, rn.buf + ret, rn.loaded - ret);
+ }
+ rn.loaded -= ret;
+ if (rn.loaded || !eof)
+ goto recv;
+out:
+ if (r_opened)
+ r->close(&rn);
+ if (r)
+ r->shutdown();
+ if (ret < 0)
+ PARA_NOTICE_LOG("%d: (%s)\n", ret, PARA_STRERROR(-ret));
+ return ret;
+}
--- /dev/null
+option "loglevel" l "set loglevel (0-6)" int typestr="level" default="4" no
+option "list_receivers" L "print list of available receivers" flag off
+option "receiver" r "Select receiver.
+
+If options for the selected receiver are given, they must
+be separated by ':' instead of white space. Example:
+
+ -r http:-i:www.paraslash.org:-p:8009
+"
+
+string typestr="receiver_spec" default="http" no
--- /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 recv.h receiver-relates structures and exported symbols of recv_common.c */
+
+/**
+ * describes one instance of a receiver
+ */
+struct receiver_node {
+ /** points to the corresponding receiver */
+ struct receiver *receiver;
+ /** the output buffer */
+ char *buf;
+ /** the amount of bytes in \a buf */
+ size_t loaded;
+ /** receiver-specific data */
+ void *private_data;
+ /** set to 1 if end of file is reached */
+ int eof;
+ /** pointer to the configuration data for this instance */
+ void *conf;
+};
+
+/**
+ * describes one supported paraslash receiver
+ *
+ * \sa http_recv.c, ortp_recv.c
+ */
+struct receiver {
+/**
+ *
+ *
+ * the name of the receiver
+ */
+ const char *name;
+/**
+ *
+ *
+ * the receiver init function
+ *
+ * It must fill in all other function pointers and is assumed to succeed.
+ *
+ * \sa http_recv_init ortp_recv_init
+ */
+ void (*init)(struct receiver *r);
+/**
+ *
+ *
+ * the command line parser of the receiver
+ *
+ * It should check whether the command line options given by \a argc and \a
+ * argv are valid. On success, it should return a pointer to the
+ * receiver-specific configuration data determined by \a argc and \a argv.
+ * Note that this might be called more than once with different values of
+ * \a argc and \a argv.
+ *
+ */
+ void * (*parse_config)(int argc, char **argv);
+/**
+ *
+ *
+ * open one instance of the receiver
+ *
+ * This should allocate the output buffer of \a rn. and may also perform any
+ * other work necessary for retrieving the stream according to the
+ * configuration stored in the \a conf member of \a rn which is guaranteed to
+ * point to valid configuration data (as previously obtained from the config
+ * parser).
+ *
+ * \sa receiver_node::conf, receiver_node::buf
+ */
+ int (*open)(struct receiver_node *rn);
+/**
+ *
+ *
+ * close one instance of the receiver
+ *
+ * It should free all resources associated with given receiver node that were
+ * allocated during the corresponding open call.
+ *
+ * \sa receiver_node
+ */
+ void (*close)(struct receiver_node *rn);
+/**
+ *
+ *
+ * deactivate the receiver
+ *
+ * Clean up what init has allocated.
+ */
+ void (*shutdown)(void);
+/**
+ *
+ *
+ * add file descriptors to fd_sets and compute timeout for select(2)
+ *
+ * The pre_select function gets called from the driving application before
+ * entering its select loop. The receiver may use this hook to add any file
+ * descriptors to \a rfds and \a wfds in order to check the result later in the
+ * post_select hook.
+ *
+ * \a timeout is a value-result parameter, initially containing the timeout for
+ * select() which was set by the application or by another receiver node. If
+ * the receiver wants its pre_select function to be called at some earlier time
+ * than the time determined by \a timeout, it may set \a timeout to an
+ * appropriate smaller value. However, it must never increase this timeout.
+ *
+ * This function must return the highest-numbered descriptor it wants to being
+ * checked, or -1 if no file descriptors should be checked for this run.
+ *
+ * \sa select(2), receiver_node:private_data, time.c
+ */
+ int (*pre_select)(struct receiver_node *rn, fd_set *rfds,
+ fd_set *wfds, struct timeval *timeout);
+/**
+ *
+ *
+ * evaluate the result from select()
+ *
+ * If the call to select() was succesful, this hook gets called. It should
+ * check all file descriptors which were added to any of the the fd sets during
+ * the previous call to pre_select. According to the result, it may then use
+ * any non-blocking I/O to establish a connection or to receive the audio data.
+ *
+ * A negative return value is interpreted as an error.
+ *
+ * \sa select(2), struct receiver
+ */
+ int (*post_select)(struct receiver_node *rn, int select_ret,
+ fd_set *rfds, fd_set *wfds);
+};
+
+
+/** \cond */
+extern void http_recv_init(struct receiver *r);
+#define HTTP_RECEIVER {.name = "http", .init = http_recv_init},
+
+#ifdef HAVE_ORTP
+extern void ortp_recv_init(struct receiver *r);
+#define ORTP_RECEIVER {.name = "ortp", .init = ortp_recv_init},
+#else
+#define ORTP_RECEIVER
+#endif
+
+void *check_receiver_arg(char *ra, int *receiver_num);
+
+
+extern struct receiver receivers[];
+extern void (*crypt_function_recv)(unsigned long len, const unsigned char *indata, unsigned char *outdata);
+extern void (*crypt_function_send)(unsigned long len, const unsigned char *indata, unsigned char *outdata);
+
+#define DEFINE_RECEIVER_ARRAY struct receiver receivers[] = { \
+ HTTP_RECEIVER \
+ ORTP_RECEIVER \
+ {.name = NULL}};
+
+/** \endcond */
--- /dev/null
+/*
+ * Copyright (C) 2006 Andre Noll <maan@systemlinux.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+ */
+
+/** \file recv_common.c common functions of para_recv and para_audiod */
+
+#include "para.h"
+
+#include "recv.h"
+#include "string.h"
+
+void (*crypt_function_recv)(unsigned long len, const unsigned char *indata, unsigned char *outdata) = NULL;
+void (*crypt_function_send)(unsigned long len, const unsigned char *indata, unsigned char *outdata) = NULL;
+
+DEFINE_RECEIVER_ARRAY;
+static void *parse_receiver_args(int receiver_num, char *options)
+{
+ struct receiver *r = &receivers[receiver_num];
+ char **argv;
+ int argc, i;
+ void *conf;
+
+
+// PARA_DEBUG_LOG("%s, options: %s\n", r->name,
+// options? options : "(none)");
+ if (options) {
+// PARA_DEBUG_LOG("%s options: %s\n", name, options);
+ argc = split_args(options, &argv, ':');
+// PARA_DEBUG_LOG("argc = %d, argv[0]: %s\n", fn->argc, fn->argv[0]);
+ for (i = argc; i >= 0; i--)
+ argv[i + 1] = argv[i];
+ argc += 2;
+ } else {
+ argc = 1;
+ argv = para_malloc(2 * sizeof(char*));
+ argv[0] = NULL;
+ argv[1] = NULL;
+ }
+ conf = r->parse_config(argc, argv);
+ if (!conf) {
+ for (i = 0; i < argc; i++)
+ free(argv[i]);
+ free(argv);
+ return NULL;
+ }
+ return conf;
+}
+
+void *check_receiver_arg(char *ra, int *receiver_num)
+{
+ int j;
+
+// PARA_DEBUG_LOG("checking %s\n", ra);
+ for (j = 0; receivers[j].name; j++) {
+ const char *name = receivers[j].name;
+ size_t len = strlen(name);
+ char c;
+ if (strlen(ra) < len)
+ continue;
+ if (strncmp(name, ra, len))
+ continue;
+ c = ra[len];
+ if (c && c != ':')
+ continue;
+ if (c && !receivers[j].parse_config)
+ return NULL;
+ *receiver_num = j;
+ return parse_receiver_args(j, c? ra + len + 1: NULL);
+ }
+ PARA_ERROR_LOG("%s", "receiver not found:");
+ return NULL;
+}
--- /dev/null
+/*
+ * Copyright (C) 2006 Andre Noll <maan@systemlinux.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+ */
+
+/** \file ringbuffer.c simple ringbuffer implementation */
+
+#include "gcc-compat.h"
+#include "para.h"
+#include "ringbuffer.h"
+#include "string.h"
+
+/**
+ * holds all information about one ring buffer
+ *
+ * It is intentionally not exported via ringbuffer.h. Think abstract.
+ */
+struct ringbuffer
+{
+/**
+ *
+ *
+ * the size of this ring buffer
+*/
+unsigned size;
+/**
+ *
+ *
+ * the actual entries of the ringbuffer
+*/
+void **entries;
+/**
+ *
+ *
+ * the next entry will be added at this position
+ */
+int head;
+/**
+ *
+ * how many entries the ring buffer contains
+*/
+unsigned filled;
+};
+
+/**
+ * initialize a new ringbuffer
+ *
+ * @param size the number of entries the ringbuffer holds
+ *
+ * This function initializes a circular ring buffer which can hold up to \a
+ * size entries of arbitrary type. If performance is an issue, \a size should
+ * be a power of two to make the underlying modulo operations cheap. Arbitrary
+ * many ringbuffers may be initialized via this function. Each ringbuffer is
+ * identified by a 'cookie'.
+ *
+ * Return value: A 'cookie' which identifies the ringbuffer just created and
+ * which must be passed to ringbuffer_add() and ringbuffer_get().
+ */
+void *ringbuffer_new(unsigned size)
+{
+ struct ringbuffer *rb = para_calloc(sizeof(struct ringbuffer));
+ rb->entries = para_calloc(size * sizeof(void *));
+ rb->size = size;
+ return rb;
+};
+
+/**
+ * add one entry to a ringbuffer
+ *
+ * @param cookie the ringbuffer identifier
+ * @param data the data to be inserted
+ *
+ * insert \a data into the ringbuffer associated with \a cookie. As soon as
+ * the ringbuffer fills up, its oldest entry is disregarded and replaced by \a
+ * data.
+ *
+ * \return The old \a data pointer which is going to be disregarded, or
+ * NULL if the ringbuffer is not yet full.
+ */
+void *ringbuffer_add(void *cookie, void *data)
+{
+ struct ringbuffer *rb = cookie;
+ void *ret = rb->entries[rb->head];
+ rb->entries[rb->head] = data;
+ rb->head = (rb->head + 1) % rb->size;
+ if (rb->filled < rb->size)
+ rb->filled++;
+ return ret;
+}
+
+/**
+ * get one entry from a ringbuffer
+ *
+ * @param cookie the ringbuffer identifier
+ * @param num the number of the entry
+ *
+ * \return A pointer to data previously added, or NULL if entry number
+ * \a num is not available. \a num counts backwards from zero, i.e.
+ * ringbuffer_get_entry(0) gets the entry which was added most recently.
+ */
+void *ringbuffer_get(void *cookie, int num)
+{
+ struct ringbuffer *rb = cookie;
+ int pos = (rb->head + rb->size - 1 - num) % rb->size;
+// fprintf(stderr, "pos = %d\n", pos);
+ return rb->entries[pos];
+}
+
+/**
+ * get the number of entries in the ring buffer
+ *
+ * @param cookie the ringbuffer identifier
+ *
+ * This function always succeeds and never returns a number greater than the
+ * size of the ring buffer.
+ */
+unsigned ringbuffer_filled(void *cookie)
+{
+ struct ringbuffer *rb = cookie;
+ return rb->filled;
+}
--- /dev/null
+/** \file ringbuffer.h exported symbols from ringbuffer.c */
+void *ringbuffer_new(unsigned size);
+void *ringbuffer_add(void *cookie, void *data);
+void *ringbuffer_get(void *cookie, int num);
+unsigned ringbuffer_filled(void *cookie);
+
--- /dev/null
+#!/bin/bash
+
+bin="para_client para_audioc para_audiod para_gui" # the binaries we need
+log="`pwd`/demo-log.$$.log"
+dir="$HOME/.paraslash"
+client_conf="$dir/client.conf"
+audioc_conf="$dir/audioc.conf"
+server=www.paraslash.org
+proj=paraslash-0.2.10
+df=$proj.tar.bz2 # download file
+url=http://$server/versions/$df
+kf="$dir/key.anonymous" # key file
+key_url=http://$server/key.anonymous
+socket="$dir/socket"
+receiver_opts="mp3:http:-i:$server:-p:8009"
+audiod_log="$dir/audiod.log"
+audiod_opts="-FDdr $receiver_opts -L $audiod_log -s $socket"
+msg()
+{
+ echo "`date`: $1"
+}
+
+go()
+{
+ msg "downloading $df"
+ wget -N -q $url || return 1
+ msg "decompressing"
+ rm -rf $proj; tar xjf $df
+ cd $proj || return 1
+ msg "configuring (be patient, ignore warnings but not errors)"
+ ./configure --prefix "$HOME" >> "$log"
+ msg "building $bin"
+ make $bin >> "$log"
+ msg "installing"
+ mkdir -p $HOME/bin && install -c -s -m 755 $bin $HOME/bin || return 1
+ export PATH=$HOME/bin:$PATH
+ msg "retrieving anonymous key"
+ mkdir -p $dir || return 1
+ wget -q --directory-prefix=$dir $key_url || return 1
+ msg "writing $client_conf"
+ cat << EOF > "$client_conf"
+ user "anonymous"
+ hostname "$server"
+ key_file "$kf"
+EOF
+ msg "writing $audioc_conf"
+ echo "socket \"$socket\"" > "$audioc_conf"
+ (para_audioc term; killall para_audiod para_client) >> "$log" 2>&1
+ msg "para_audiod $audiod_opts"
+ para_audiod $audiod_opts -w "mp3:mpg123 -"
+ echo "hit return to start para_gui, hit ctrl+c to abort"
+ read
+ para_gui
+}
+
+go
--- /dev/null
+/*
+ * Copyright (C) 2003-2006 Andre Noll <maan@systemlinux.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+ */
+
+/** \file sdl_gui.c SDL-based interface for paraslash */
+
+#include "para.h"
+#include "string.h"
+
+
+#include <SDL/SDL.h>
+#include "SFont.h"
+
+#include <SDL/SDL_image.h>
+#include <SDL/SDL_events.h>
+#include <SDL/SDL_keyboard.h>
+#include <SDL/SDL_keysym.h>
+#include <sys/time.h> /* timeval, needed for select */
+
+#include "sdl_gui.cmdline.h"
+
+#define FRAME_RED 100
+#define FRAME_GREEN 200
+#define FRAME_BLUE 200
+
+SDL_Surface *screen;
+static int width = 0;
+static int height = 0;
+
+extern const char *status_item_list[NUM_STAT_ITEMS];
+struct gengetopt_args_info args_info;
+
+#define FRAME_WIDTH 10
+#define FONT_HEIGHT 36
+
+#define INPUT_WIDTH width - 2 * FRAME_WIDTH
+#define INPUT_X FRAME_WIDTH
+#define INPUT_HEIGHT (args_info.interactive_flag? FONT_HEIGHT : 0)
+#define INPUT_Y height - FRAME_WIDTH - INPUT_HEIGHT
+
+#define OUTPUT_WIDTH width - 2 * FRAME_WIDTH
+#define OUTPUT_X FRAME_WIDTH
+#define OUTPUT_Y FRAME_WIDTH
+#define OUTPUT_HEIGHT (height - INPUT_HEIGHT - 2 * FRAME_WIDTH)
+
+#define NUM_LINES (height - 2 * FRAME_WIDTH - INPUT_HEIGHT) / FONT_HEIGHT
+
+#define M_YELLOW 0
+#define N_YELLOW 1
+
+#define LEFT 1
+#define RIGHT 2
+#define CENTER 3
+
+
+struct stat_item{
+ char *name;
+ char *prefix;
+ char *postfix;
+ char *content;
+ unsigned x;
+ unsigned y;
+ unsigned w;
+ unsigned h;
+ Uint8 r;
+ Uint8 g;
+ Uint8 b;
+ int font;
+ int align;
+};
+
+struct font {
+ char name[MAXLINE];
+ SFont_FontInfo fontinfo;
+};
+
+struct font fonts[] = {
+ {
+ .name = "24P_Arial_Metallic_Yellow.png",
+ .fontinfo = {NULL, {0}, 0}
+ },
+ {
+ .name = "24P_Arial_NeonYellow.png",
+ .fontinfo = {NULL, {0}, 0}
+ },
+ {
+ .name = "24P_Copperplate_Blue.png",
+ .fontinfo = {NULL, {0}, 0}
+ },
+ {
+ .name = "",
+ .fontinfo = {NULL, {0}, 0}
+ }
+};
+
+
+#define PIC_WIDTH width * 3 / 10
+#define PIC_HEIGHT height / 3
+
+#define INPUT_FONT 0
+#define OUTPUT_FONT 1
+#define MSG_FONT 2
+
+
+static struct stat_item stat_items[NUM_STAT_ITEMS];
+
+void para_log(__unused int ll, __unused char* fmt,...) /* no logging */
+{
+}
+
+static void init_stat_items(void)
+{
+ int i;
+ struct stat_item *s = stat_items;
+
+ for (i = 0; i < NUM_STAT_ITEMS; i++) {
+ s[i].w = 0;
+ s[i].content = NULL;
+ }
+
+
+ s[SI_STATUS_BAR].prefix = "";
+ s[SI_STATUS_BAR].postfix = "";
+ s[SI_STATUS_BAR].content = "";
+ s[SI_STATUS_BAR].x = 0;
+ s[SI_STATUS_BAR].y = 10;
+ s[SI_STATUS_BAR].w = 100;
+ s[SI_STATUS_BAR].h = FONT_HEIGHT;
+ s[SI_STATUS_BAR].r = 0;
+ s[SI_STATUS_BAR].g = 0;
+ s[SI_STATUS_BAR].b = 0;
+ s[SI_STATUS_BAR].font = M_YELLOW;
+ s[SI_STATUS_BAR].align = CENTER;
+
+ s[SI_PLAY_TIME].prefix = "";
+ s[SI_PLAY_TIME].postfix = "";
+ s[SI_PLAY_TIME].content = "";
+ s[SI_PLAY_TIME].x = 35;
+ s[SI_PLAY_TIME].y = 20;
+ s[SI_PLAY_TIME].w = 65;
+ s[SI_PLAY_TIME].h = FONT_HEIGHT;
+ s[SI_PLAY_TIME].r = 0;
+ s[SI_PLAY_TIME].g = 0;
+ s[SI_PLAY_TIME].b = 0;
+ s[SI_PLAY_TIME].font = M_YELLOW;
+ s[SI_PLAY_TIME].align = CENTER;
+
+ s[SI_STATUS].prefix = "";
+ s[SI_STATUS].postfix = "";
+ s[SI_STATUS].content = "";
+ s[SI_STATUS].x = 35;
+ s[SI_STATUS].y = 28;
+ s[SI_STATUS].w = 12;
+ s[SI_STATUS].h = FONT_HEIGHT;
+ s[SI_STATUS].r = 0;
+ s[SI_STATUS].g = 0;
+ s[SI_STATUS].b = 0;
+ s[SI_STATUS].font = N_YELLOW;
+ s[SI_STATUS].align = LEFT;
+
+ s[SI_STATUS_FLAGS].prefix = " (";
+ s[SI_STATUS_FLAGS].postfix = ")";
+ s[SI_STATUS_FLAGS].content = "";
+ s[SI_STATUS_FLAGS].x = 47;
+ s[SI_STATUS_FLAGS].y = 28;
+ s[SI_STATUS_FLAGS].w = 15;
+ s[SI_STATUS_FLAGS].h = FONT_HEIGHT;
+ s[SI_STATUS_FLAGS].r = 0;
+ s[SI_STATUS_FLAGS].g = 0;
+ s[SI_STATUS_FLAGS].b = 0;
+ s[SI_STATUS_FLAGS].font = N_YELLOW;
+ s[SI_STATUS_FLAGS].align = CENTER;
+
+ s[SI_NUM_PLAYED].prefix = "#";
+ s[SI_NUM_PLAYED].postfix = "";
+ s[SI_NUM_PLAYED].content = "0";
+ s[SI_NUM_PLAYED].x = 62;
+ s[SI_NUM_PLAYED].y = 28;
+ s[SI_NUM_PLAYED].w = 13;
+ s[SI_NUM_PLAYED].h = FONT_HEIGHT;
+ s[SI_NUM_PLAYED].r = 0;
+ s[SI_NUM_PLAYED].g = 0;
+ s[SI_NUM_PLAYED].b = 0;
+ s[SI_NUM_PLAYED].font = N_YELLOW;
+ s[SI_NUM_PLAYED].align = CENTER;
+
+ s[SI_UPTIME].prefix = "Up: ";
+ s[SI_UPTIME].postfix = "";
+ s[SI_UPTIME].content = "";
+ s[SI_UPTIME].x = 75;
+ s[SI_UPTIME].y = 28;
+ s[SI_UPTIME].w = 25;
+ s[SI_UPTIME].h = FONT_HEIGHT;
+ s[SI_UPTIME].r = 0;
+ s[SI_UPTIME].g = 0;
+ s[SI_UPTIME].b = 0;
+ s[SI_UPTIME].font = N_YELLOW;
+ s[SI_UPTIME].align = RIGHT;
+
+ s[SI_DBTOOL].prefix = "dbtool: ";
+ s[SI_DBTOOL].postfix = "";
+ s[SI_DBTOOL].content = "no content yet";
+ s[SI_DBTOOL].x = 35;
+ s[SI_DBTOOL].y = 48;
+ s[SI_DBTOOL].w = 35;
+ s[SI_DBTOOL].h = FONT_HEIGHT;
+ s[SI_DBTOOL].r = 0;
+ s[SI_DBTOOL].g = 0;
+ s[SI_DBTOOL].b = 0;
+ s[SI_DBTOOL].font = N_YELLOW;
+ s[SI_DBTOOL].align = LEFT;
+
+ s[SI_FORMAT].prefix = "Format: ";
+ s[SI_FORMAT].postfix = "";
+ s[SI_FORMAT].content = "";
+ s[SI_FORMAT].x = 70;
+ s[SI_FORMAT].y = 48;
+ s[SI_FORMAT].w = 30;
+ s[SI_FORMAT].h = FONT_HEIGHT;
+ s[SI_FORMAT].r = 0;
+ s[SI_FORMAT].g = 0;
+ s[SI_FORMAT].b = 0;
+ s[SI_FORMAT].font = N_YELLOW;
+ s[SI_FORMAT].align = RIGHT;
+
+ s[SI_MTIME].prefix = "MTime: ";
+ s[SI_MTIME].postfix = "";
+ s[SI_MTIME].content = "";
+ s[SI_MTIME].x = 35;
+ s[SI_MTIME].y = 35;
+ s[SI_MTIME].w = 65;
+ s[SI_MTIME].h = FONT_HEIGHT;
+ s[SI_MTIME].r = 0;
+ s[SI_MTIME].g = 0;
+ s[SI_MTIME].b = 0;
+ s[SI_MTIME].font = N_YELLOW;
+ s[SI_MTIME].align = LEFT;
+
+ s[SI_FILE_SIZE].prefix = "Size: ";
+ s[SI_FILE_SIZE].postfix = "kb";
+ s[SI_FILE_SIZE].content = "";
+ s[SI_FILE_SIZE].x = 35;
+ s[SI_FILE_SIZE].y = 42;
+ s[SI_FILE_SIZE].w = 20;
+ s[SI_FILE_SIZE].h = FONT_HEIGHT;
+ s[SI_FILE_SIZE].r = 0;
+ s[SI_FILE_SIZE].g = 0;
+ s[SI_FILE_SIZE].b = 0;
+ s[SI_FILE_SIZE].font = N_YELLOW;
+ s[SI_FILE_SIZE].align = LEFT;
+
+ s[SI_AUDIO_INFO1].prefix = "";
+ s[SI_AUDIO_INFO1].postfix = "";
+ s[SI_AUDIO_INFO1].content = "";
+ s[SI_AUDIO_INFO1].x = 0;
+ s[SI_AUDIO_INFO1].y = 60;
+ s[SI_AUDIO_INFO1].w = 100;
+ s[SI_AUDIO_INFO1].h = FONT_HEIGHT;
+ s[SI_AUDIO_INFO1].r = 0;
+ s[SI_AUDIO_INFO1].g = 0;
+ s[SI_AUDIO_INFO1].b = 0;
+ s[SI_AUDIO_INFO1].font = N_YELLOW;
+ s[SI_AUDIO_INFO1].align = CENTER;
+
+ s[SI_AUDIO_INFO2].prefix = "";
+ s[SI_AUDIO_INFO2].postfix = "";
+ s[SI_AUDIO_INFO2].content = "";
+ s[SI_AUDIO_INFO2].x = 0;
+ s[SI_AUDIO_INFO2].y = 65;
+ s[SI_AUDIO_INFO2].w = 100;
+ s[SI_AUDIO_INFO2].h = FONT_HEIGHT;
+ s[SI_AUDIO_INFO2].r = 0;
+ s[SI_AUDIO_INFO2].g = 0;
+ s[SI_AUDIO_INFO2].b = 0;
+ s[SI_AUDIO_INFO2].font = N_YELLOW;
+ s[SI_AUDIO_INFO2].align = CENTER;
+
+ s[SI_AUDIO_INFO3].prefix = "";
+ s[SI_AUDIO_INFO3].postfix = "";
+ s[SI_AUDIO_INFO3].content = "";
+ s[SI_AUDIO_INFO3].x = 0;
+ s[SI_AUDIO_INFO3].y = 70;
+ s[SI_AUDIO_INFO3].w = 100;
+ s[SI_AUDIO_INFO3].h = FONT_HEIGHT;
+ s[SI_AUDIO_INFO3].r = 0;
+ s[SI_AUDIO_INFO3].g = 0;
+ s[SI_AUDIO_INFO3].b = 0;
+ s[SI_AUDIO_INFO3].font = N_YELLOW;
+ s[SI_AUDIO_INFO3].align = CENTER;
+
+ s[SI_DBINFO1].name = "dbinfo1:";
+ s[SI_DBINFO1].prefix = "";
+ s[SI_DBINFO1].postfix = "";
+ s[SI_DBINFO1].content = "";
+ s[SI_DBINFO1].x = 0;
+ s[SI_DBINFO1].y = 83;
+ s[SI_DBINFO1].w = 100;
+ s[SI_DBINFO1].h = FONT_HEIGHT;
+ s[SI_DBINFO1].r = 0;
+ s[SI_DBINFO1].g = 0;
+ s[SI_DBINFO1].b = 0;
+ s[SI_DBINFO1].font = N_YELLOW;
+ s[SI_DBINFO1].align = CENTER;
+
+ s[SI_DBINFO2].prefix = "";
+ s[SI_DBINFO2].postfix = "";
+ s[SI_DBINFO2].content = "";
+ s[SI_DBINFO2].x = 0;
+ s[SI_DBINFO2].y = 88;
+ s[SI_DBINFO2].w = 100;
+ s[SI_DBINFO2].h = FONT_HEIGHT;
+ s[SI_DBINFO2].r = 0;
+ s[SI_DBINFO2].g = 0;
+ s[SI_DBINFO2].b = 0;
+ s[SI_DBINFO2].font = N_YELLOW;
+ s[SI_DBINFO2].align = CENTER;
+
+ s[SI_DBINFO3].name = "dbinfo3:";
+ s[SI_DBINFO3].prefix = "";
+ s[SI_DBINFO3].postfix = "";
+ s[SI_DBINFO3].content = "";
+ s[SI_DBINFO3].x = 0;
+ s[SI_DBINFO3].y = 93;
+ s[SI_DBINFO3].w = 100;
+ s[SI_DBINFO3].h = FONT_HEIGHT;
+ s[SI_DBINFO3].r = 0;
+ s[SI_DBINFO3].g = 0;
+ s[SI_DBINFO3].b = 0;
+ s[SI_DBINFO3].font = N_YELLOW;
+ s[SI_DBINFO3].align = CENTER;
+}
+
+/*
+ * init SDL libary and set window title
+ */
+static void init_SDL(void)
+{
+ if (SDL_Init(SDL_INIT_VIDEO) == -1) {
+ fprintf(stderr,
+ "Couldn't initialize SDL: %s\n", SDL_GetError());
+ exit(1);
+ }
+ /* Clean up on exit */
+ atexit(SDL_Quit);
+ /* Initialize the display */
+ if (args_info.fullscreen_flag)
+ screen = SDL_SetVideoMode(width, height, 0, SDL_FULLSCREEN);
+ else
+ screen = SDL_SetVideoMode(width, height, 0, 0);
+ if (!screen) {
+ fprintf(stderr, "Couldn't set video mode: %s\n",
+ SDL_GetError());
+ exit(1);
+ }
+ SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE);
+ SDL_EventState(SDL_MOUSEBUTTONDOWN, SDL_IGNORE);
+ SDL_EventState(SDL_MOUSEBUTTONUP, SDL_IGNORE);
+ /* Set the window manager title bar */
+ SDL_WM_SetCaption("The Gui of death that makes you blind (paraslash "
+ VERSION ")", "SFont");
+}
+
+/*
+ * draw rectangular frame of width FRAME_WIDTH
+ */
+static void draw_frame(Uint8 r, Uint8 g, Uint8 b) {
+ SDL_Rect rect;
+
+ rect.x = 0;
+ rect.y = 0;
+ rect.w = width;
+ rect.h = FRAME_WIDTH;
+ SDL_FillRect(screen, &rect, SDL_MapRGB(screen->format, r, g, b));
+ SDL_UpdateRect(screen, rect.x, rect.y, rect.w, rect.h);
+
+ rect.x = 0;
+ rect.y = height - FRAME_WIDTH;
+ rect.w = width;
+ rect.h = FRAME_WIDTH;
+ SDL_FillRect(screen, &rect, SDL_MapRGB(screen->format, r, g, b));
+ SDL_UpdateRect(screen, rect.x, rect.y, rect.w, rect.h);
+
+ rect.x = 0;
+ rect.y = FRAME_WIDTH;
+ rect.w = FRAME_WIDTH;
+ rect.h = height - 2 * FRAME_WIDTH;
+ SDL_FillRect(screen, &rect, SDL_MapRGB(screen->format, r, g, b));
+ SDL_UpdateRect(screen, rect.x, rect.y, rect.w, rect.h);
+
+ rect.x = width - FRAME_WIDTH;
+ rect.y = FRAME_WIDTH;
+ rect.w = FRAME_WIDTH;
+ rect.h = height - 2 * FRAME_WIDTH;
+ SDL_FillRect(screen, &rect, SDL_MapRGB(screen->format, r, g, b));
+ SDL_UpdateRect(screen, rect.x, rect.y, rect.w, rect.h);
+}
+
+/*
+ * fill input rect with color
+ */
+static void fill_input_rect(void)
+{
+ SDL_Rect rect;
+
+ rect.x = INPUT_X;
+ rect.y = INPUT_Y;
+ rect.w = INPUT_WIDTH;
+ rect.h = INPUT_HEIGHT;
+ SDL_FillRect(screen, &rect, SDL_MapRGB(screen->format, 10, 150, 10));
+}
+
+/*
+ * fill output rect with color
+ */
+static void fill_output_rect(void)
+{
+ SDL_Rect rect;
+
+ rect.x = OUTPUT_X;
+ rect.y = OUTPUT_Y;
+ rect.w = OUTPUT_WIDTH;
+ rect.h = OUTPUT_HEIGHT;
+ SDL_FillRect(screen, &rect, SDL_MapRGB(screen->format, 0, 0, 0));
+}
+
+/*
+ * convert tab to space
+ */
+static void tab2space(char *text)
+{
+ char *p = text;
+ while (*p) {
+ if (*p == '\t')
+ *p = ' ';
+ p++;
+ }
+}
+
+static void print_msg(char *msg)
+{
+ SFont_FontInfo *font = &(fonts[MSG_FONT].fontinfo);
+ char *buf = strdup(msg);
+ int len = strlen(buf);
+
+ if (!buf)
+ return;
+ while (TextWidth2(font, buf) > INPUT_WIDTH && len > 0) {
+ *(buf + len) = '\0';
+ len--;
+ }
+ fill_input_rect();
+ PutString2(screen, font, INPUT_X, INPUT_Y, buf);
+ free(buf);
+}
+
+static void update_all(void)
+{
+ SDL_UpdateRect(screen, 0, 0, 0, 0);
+}
+
+static void update_input(void)
+{
+ SDL_UpdateRect(screen, INPUT_X, INPUT_Y, INPUT_WIDTH, INPUT_HEIGHT);
+
+}
+
+/*
+ * wait for key, ignore all other events, return 0 if there is no key event
+ * pending. Otherwise return keysym of key
+ */
+SDLKey get_key(void)
+{
+ SDL_Event event;
+
+ while (SDL_PollEvent(&event) > 0) {
+ if(event.type != SDL_KEYDOWN)
+ continue;
+// printf("Key pressed, scancode: 0x%x\n",
+// event.key.keysym.scancode);
+ return event.key.keysym.sym;
+ }
+ return 0;
+}
+
+/*
+ * print message, wait for key (blocking), return 1 for 'q', 0 else
+ */
+static SDLKey hit_key(char *msg)
+{
+ SDLKey sym;
+
+ print_msg(msg);
+ update_input();
+ while (!(sym = get_key()))
+ ;
+ fill_input_rect();
+ update_input();
+ if (sym == SDLK_q)
+ return 1;
+ else
+ return 0;
+}
+
+/*
+ * read paraslash command from input, execute it and print results
+ */
+static int command_handler(void)
+{
+ FILE *pipe;
+ unsigned count = 0;
+ char text[MAXLINE]="";
+ char buf[MAXLINE]="";
+ SFont_FontInfo *font = &fonts[OUTPUT_FONT].fontinfo;
+
+// printf("string input\n");
+ SFont_Input2(screen, &fonts[INPUT_FONT].fontinfo,
+ INPUT_X, INPUT_Y - 5, INPUT_WIDTH, text);
+ if (!strlen(text))
+ return 1;
+ if (!strcmp(text, "exit") || !strcmp(text, "quit"))
+ return 0;
+ if (text[0] == '!') {
+ if (text[1] == '\0')
+ return 1;
+ pipe = popen(text + 1, "r");
+ } else {
+ sprintf(buf, BINDIR "/para_client %s 2>&1", text);
+ pipe = popen(buf, "r");
+ }
+ if (!pipe)
+ return 0;
+ fill_output_rect();
+ while(fgets(text, MAXLINE - 1, pipe)) {
+ int len;
+
+ tab2space(text);
+ len = strlen(text);
+ // printf("string: %s\n", dest);
+ while (TextWidth2(font, text) > width - 2 * FRAME_WIDTH &&
+ len > 0) {
+ text[len] = '\0';
+ len--;
+ }
+ PutString2(screen, font, OUTPUT_X,
+ OUTPUT_Y + count * FONT_HEIGHT, text);
+ count++;
+ if (count >= NUM_LINES) {
+ update_all();
+ if (hit_key("Hit any key to continue, q to return"))
+ goto out;
+ count = 0;
+ fill_output_rect();
+ }
+ }
+ update_all();
+ hit_key("Hit any key to return");
+out: fill_output_rect();
+ pclose(pipe);
+ return 1;
+}
+
+
+/*
+ * Add prefix and postfix to string, delete characters from the end
+ * if its length exceeds the max length defined in stat_items[item]
+ */
+char *transform_string(int item)
+{
+ struct stat_item s = stat_items[item];
+ size_t len;
+ char *ret;
+ unsigned pixels = s.w * (width - 2 * FRAME_WIDTH) / 100;
+ SFont_FontInfo *font = &(fonts[s.font].fontinfo);
+
+ ret = make_message("%s%s%s", s.prefix, s.content, s.postfix);
+ len = strlen(ret);
+ while (TextWidth2(font, ret) > pixels && len > 0) {
+ *(ret + len) = '\0';
+ len--;
+ }
+ return ret;
+}
+
+SDL_Surface *load_jpg(void)
+{
+ SDL_RWops *rwop;
+ int fds[3] = {0, 1, 0};
+ pid_t pid;
+ FILE *pipe;
+
+ if (para_exec_cmdline_pid(&pid, args_info.pic_cmd_arg, fds) < 0)
+ return NULL;
+ pipe = fdopen(fds[1], "r");
+ if (!pipe)
+ return NULL;
+ if (!(rwop = SDL_RWFromFP(pipe, 0)))
+ return NULL;
+ return IMG_LoadJPG_RW(rwop);
+}
+
+void update_pic(void)
+{
+ SDL_Surface *img;
+ SDL_Rect src_pic_rect = {
+ .x = 0,
+ .y = 0,
+ .w = PIC_WIDTH,
+ .h = PIC_HEIGHT,
+ };
+ SDL_Rect dest_pic_rect = {
+ .x = FRAME_WIDTH,
+ .y = OUTPUT_HEIGHT / 5,
+ .w = PIC_WIDTH,
+ .h = PIC_HEIGHT,
+ };
+
+ if (!screen)
+ return;
+
+ if (!(img = load_jpg()))
+ return;
+ SDL_FillRect(screen, &dest_pic_rect, SDL_MapRGB(screen->format,
+ 0, 0, 0));
+ SDL_BlitSurface(img, &src_pic_rect, screen, &dest_pic_rect);
+ SDL_Flip(screen);
+ SDL_FreeSurface(img);
+}
+
+/*
+ * update status item number i.
+ */
+static void do_update(int i)
+{
+ static int last_played = -1;
+ SDL_Rect rect;
+ char *buf;
+ SFont_FontInfo *font = &(fonts[stat_items[i].font].fontinfo);
+ if (!stat_items[i].w)
+ return;
+
+ rect.x = stat_items[i].x * (width - FRAME_WIDTH * 2) / 100
+ + FRAME_WIDTH;
+ rect.y = stat_items[i].y * (height - 2 * FRAME_WIDTH - INPUT_HEIGHT)
+ / 100;
+ rect.w = stat_items[i].w * (width - 2 * FRAME_WIDTH) / 100;
+ rect.h = stat_items[i].h;
+ buf = transform_string(i);
+ SDL_FillRect(screen, &rect, SDL_MapRGB(screen->format,
+ stat_items[i].r, stat_items[i].g, stat_items[i].b));
+ switch(stat_items[i].align) {
+ case CENTER:
+ PutString2(screen, font,
+ rect.x + (rect.w - TextWidth2(font, buf)) / 2,
+ rect.y, buf);
+ break;
+ case LEFT:
+ PutString2(screen, font, rect.x, rect.y, buf);
+ break;
+ case RIGHT:
+ PutString2(screen, font, rect.x + (rect.w -
+ TextWidth2(font, buf)), rect.y, buf);
+ break;
+ }
+ free(buf);
+ SDL_UpdateRect(screen, rect.x, rect.y, rect.w, rect.h);
+ if (i == SI_NUM_PLAYED && atoi(stat_items[i].content) != last_played) {
+ update_pic();
+ last_played = atoi(stat_items[i].content);
+ };
+}
+
+/*
+ * Check if buf is a known status line. If so call do_update and return 1.
+ * Return 0 otherwise.
+ */
+void update_status(char *buf)
+{
+ int i;
+
+ i = stat_line_valid(buf);
+ if (i < 0)
+ return;
+ //free(stat_items[i].content);
+ stat_items[i].content = para_strdup(buf +
+ strlen(status_item_list[i]) + 1);
+ do_update(i);
+}
+
+/*
+ * Read stat line from pipe if pipe is ready, call update_status to
+ * display information.
+ */
+static int draw_status(int pipe)
+{
+ fd_set rfds;
+ int ret;
+ struct timeval tv;
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 3000000;
+ FD_ZERO(&rfds);
+ FD_SET(pipe, &rfds);
+ ret = select(pipe + 1, &rfds, NULL, NULL, &tv);
+// printf("select returned %d\n", ret);
+ if (ret <= 0)
+ return 0;
+ if (read_audiod_pipe(pipe, update_status) > 0)
+ return 1;
+// clear_all_items();
+ free(stat_items[SI_STATUS_BAR].content);
+ stat_items[SI_STATUS_BAR].content =
+ para_strdup("audiod not running!?\n");
+ update_all();
+ sleep(1);
+ return -1;
+}
+
+static void clean_exit(int ret)
+{
+ SDL_Quit();
+ exit(ret);
+}
+
+static void print_help(void)
+{
+ print_msg("Hit q to quit, any other key to enter command mode");
+}
+
+static int configfile_exists(void)
+{
+ if (!args_info.config_file_given) {
+ char *home = para_homedir();
+ args_info.config_file_arg = make_message(
+ "%s/.paraslash/sdl_gui.conf", home);
+ free(home);
+ }
+ return file_exists(args_info.config_file_arg);
+}
+
+/*
+ * MAIN
+ */
+int main(int argc, char *argv[])
+{
+ int i, ret, pipe;
+ SDLKey sym;
+
+ cmdline_parser(argc, argv, &args_info);
+ ret = configfile_exists();
+// printf("w=%i,h=%i,ret=%i, cf=%s\n", width, height, ret, args_info.config_file_arg);
+
+ if (!ret && args_info.config_file_given) {
+ fprintf(stderr, "Can't read config file %s\n",
+ args_info.config_file_arg);
+ exit(EXIT_FAILURE);
+ }
+ if (ret)
+ cmdline_parser_configfile(args_info.config_file_arg,
+ &args_info, 0, 0, 0);
+ signal(SIGCHLD, SIG_IGN);
+ width = args_info.width_arg;
+ height = args_info.height_arg;
+// printf("w=%i,h=%i,ret=%i, cf=%s\n", width, height, ret, args_info.config_file_arg);
+ init_stat_items();
+ pipe = para_open_audiod_pipe(args_info.stat_cmd_arg);
+ init_SDL();
+ for (i = 0; fonts[i].name[0]; i++) {
+ char buf[MAXLINE];
+ sprintf(buf, "%s/%s", FONTDIR, fonts[i].name);
+ /* Load the font - You don't have to use the IMGlib for this */
+ fonts[i].fontinfo.Surface = IMG_Load(buf);
+ /* Prepare the font for use */
+ InitFont2(&fonts[i].fontinfo);
+ }
+ draw_frame(FRAME_RED, FRAME_GREEN, FRAME_BLUE);
+ if (args_info.interactive_flag) {
+ print_help();
+ update_input();
+ }
+ for (;;) {
+ ret = draw_status(pipe);
+ if (ret < 0) {
+ close(pipe);
+ pipe = -1;
+ }
+ if (SDL_QuitRequested())
+ clean_exit(0);
+ while ((sym = get_key())) {
+ if (!args_info.interactive_flag)
+ clean_exit(0);
+ if (sym == SDLK_q)
+ clean_exit(0);
+ if ( sym == SDLK_LSHIFT
+ || sym == SDLK_RSHIFT
+ || sym == SDLK_LMETA
+ || sym == SDLK_RMETA
+ || sym == SDLK_RCTRL
+ || sym == SDLK_LCTRL
+ || sym == SDLK_MODE
+ || sym == SDLK_CAPSLOCK
+ || sym == SDLK_LALT
+ || sym == SDLK_RALT
+ || sym == SDLK_RSUPER
+ || sym == SDLK_LSUPER
+ || sym == SDLK_COMPOSE
+ )
+ continue;
+ if (pipe < 0) {
+// printf("closing pipe\n");
+ kill(0, SIGINT);
+ close(pipe);
+// printf("pipe closed\n");
+ }
+ fill_input_rect();
+ update_input();
+ if (!command_handler())
+ clean_exit(0);
+ fill_output_rect();
+ print_help();
+ update_pic();
+ SDL_UpdateRect(screen, 0, 0, 0, 0);
+ pipe = para_open_audiod_pipe(args_info.stat_cmd_arg);
+ break;
+ }
+ }
+}
--- /dev/null
+# file sample1.ggo
+option "fullscreen" f "Use fullscreeen mode" flag off
+option "interactive" i "Activate interactive mode" flag off
+option "width" x "Specify screen width" int typestr="pixels" default="1024" no
+option "height" y "Specify screen height" int typestr="pixels" default="768" no
+option "config_file" c "(default='~/.paraslash/sdl_gui.conf')" string typestr="filename" no
+option "window-id" w "(currently ignored)" string typestr="filename" no
+option "stat_cmd" s "command to read server and audiod status data from" string typestr="command" default="para_audioc -t 100 stat" no
+option "pic_cmd" p "command to read pic from" string typestr="command" default="para_client pic" no
--- /dev/null
+/** \file send.h sender-related defines and structures */
+/** the sender subcommands */
+enum {SENDER_ADD, SENDER_DELETE, SENDER_ALLOW, SENDER_DENY, SENDER_ON, SENDER_OFF};
+
+/** the number of sender subcommands */
+#define NUM_SENDER_CMDS (SENDER_OFF + 1)
+
+/**
+ * describes one supported sender of para_server
+ *
+ * \sa http_send.c ortp_send.c
+ */
+struct sender {
+/** the name of the sender */
+ const char *name;
+/**
+ * the init function of this sender
+ *
+ * It must fill in all function pointers of \a s as well as the \a client_cmds
+ * array, see below. It should also do all neccessary preparations to init
+ * this sending facility, for example it could open a tcp port.
+ */
+ void (*init)(struct sender *s);
+/** \p SENDER_ON or \p SENDER_OFF */
+ int status;
+/**
+ * return the help text of this sender
+ *
+ * The result must be dynamically allocated and is freed by the caller.
+ */
+ char* (*help)(void);
+/**
+ * return current status info about this sender
+ *
+ * The result must be dynamically allocated and is freed by the caller.
+ */
+ char* (*info)(void);
+/**
+ * the send-hook
+ *
+ * It gets called whenever para_server is playing and the current
+ * audio format handler indicates that another chunk of data should
+ * be sent now. The two parameters \a current_chunk and \a chunks_sent
+ * only differ if the stream was repositioned by the \a ff or \a jmp
+ * command. Of course, \a buf is a pointer to the chunk of data which
+ * should be sent, and \a len is the length of this buffer.
+*/
+ void (*send)(struct audio_format *af, long unsigned current_chunk,
+ long unsigned chunks_sent, const char *buf, size_t len);
+/** add file descriptors to fd_sets
+ *
+ * The pre_select function of each supported sender is called just before
+ * para_server enters its main select loop. Each sender may add its own
+ * file descriptors to the \a rfds or the \a wfds set.
+ *
+ * If a file descriptor was added, \a max_fileno must be increased by
+ * this function, if neccessary.
+ *
+ * \sa select(2)
+*/
+ void (*pre_select)(struct audio_format *af, int *max_fileno, fd_set *rfds,
+ fd_set *wfds);
+/**
+ * handle the file descriptors which are ready for I/O
+ *
+ * If the pre_select hook added one ore more file descriptors to the read or write
+ * set, this is the hook to check the result and do any I/O on those descriptors
+ * which are ready for reading/writing.
+ */
+ void (*post_select)(struct audio_format *af, fd_set *rfds, fd_set *wfds);
+/**
+ * terminate all connected clients
+ *
+ * This is called e.g. if the stop command was executed. It should make the clients
+ * aware of the end-of-file condition.
+ */
+ void (*shutdown_clients)(void);
+/**
+ * array of function pointers for the sender subcommands
+ *
+ * Each sender may implement any subset of the sender commands by filling in
+ * the aprropriate function pointer in the array. A \p NULL pointer means this
+ * command is not implemented by this sender.
+ */
+ int (*client_cmds[NUM_SENDER_CMDS])(struct sender_command_data*);
+};
--- /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 server.c Paraslash's main server */
+
+
+/** \mainpage Paraslash API Reference
+ *
+ * Good starting points for reading are probably \ref dbtool, \ref sender,
+ * \ref receiver, \ref receiver_node, \ref filter, \ref filter_node.
+ *
+ */
+
+
+
+#include "server.cmdline.h"
+#include "db.h"
+#include "server.h"
+#include "afs.h"
+#include "config.h"
+#include "close_on_fork.h"
+#include <sys/mman.h> /* MAP_SHARED, PROT_READ, PROT_WRITE */
+#include "send.h"
+#include "error.h"
+#include "net.h"
+#include "daemon.h"
+#include "string.h"
+
+/** define the array of error lists needed by para_server */
+INIT_SERVER_ERRLISTS;
+
+/** shut down non-authorized connections after that many seconds */
+#define ALARM_TIMEOUT 10
+
+/* these are exported to afs/command/dbtool */
+struct misc_meta_data *mmd;
+/** the configuration of para_server
+ *
+ * It also contains the options for all database tools and all supported
+ * senders.
+*/
+struct gengetopt_args_info conf;
+char *user_list = NULL;
+extern void http_send_init(struct sender *);
+extern void ortp_send_init(struct sender *);
+extern struct audio_format afl[];
+
+/** the list of supported database tools */
+struct dbtool dblist[] = {
+ {
+ .name = "dopey",
+ .init = dopey_dbtool_init,
+ .update_audio_file = NULL,
+ },
+#ifdef HAVE_MYSQL
+ {
+ .name = "mysql",
+ .init = mysql_dbtool_init,
+ .update_audio_file = NULL,
+ },
+#endif
+ {
+ .name = NULL,
+ }
+};
+
+/** the list of supported senders */
+struct sender senders[] = {
+ {
+ .name = "http",
+ .init = http_send_init,
+ },
+#ifdef HAVE_ORTP
+ {
+ .name = "ortp",
+ .init = ortp_send_init,
+ },
+#endif
+ {
+ .name = NULL,
+ }
+};
+
+
+/* global variables for server-internal use */
+static FILE *logfile;
+static int mmd_semid;
+static int signal_pipe;
+
+/**
+ * para_server's log function
+ *
+ * \param ll the log level
+ * \param fmt the format string describing the log message
+ */
+void para_log(int ll, char* fmt,...)
+{
+ va_list argp;
+ FILE *outfd;
+ struct tm *tm;
+ time_t t1;
+ char str[MAXLINE] = "";
+ pid_t mypid;
+
+ if (ll < conf.loglevel_arg)
+ return;
+ if (!logfile) {
+ if (ll < WARNING)
+ outfd = stdout;
+ else
+ outfd = stderr;
+ } else
+ outfd = logfile;
+ if (conf.daemon_given && !logfile)
+ return;
+ time(&t1);
+ tm = localtime(&t1);
+ strftime(str, MAXLINE, "%b %d %H:%M:%S", tm);
+ fprintf(outfd, "%s ", str);
+ if (conf.loglevel_arg <= INFO)
+ fprintf(outfd, "%i: ", ll);
+ mypid = getpid();
+ if (conf.loglevel_arg <= INFO)
+ fprintf(outfd, "(%d) ", mypid);
+ va_start(argp, fmt);
+ vfprintf(outfd, fmt, argp);
+ va_end(argp);
+}
+
+
+/*
+ * setup shared memory area and get semaphore for locking
+ */
+static void shm_init(void)
+{
+ int fd;
+ caddr_t area;
+
+ if ((fd = open("/dev/zero", O_RDWR)) < 0) {
+ PARA_EMERG_LOG("%s", "failed to open /dev/zero\n");
+ exit(EXIT_FAILURE);
+ }
+ if ((area = mmap(0, sizeof(struct misc_meta_data),
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED, fd, 0)) == (caddr_t) - 1) {
+ PARA_EMERG_LOG("%s", "mmap error\n");
+ exit(EXIT_FAILURE);
+ }
+ close(fd); /* we dont need /dev/zero anymore */
+ mmd = (struct misc_meta_data *)area;
+
+ mmd->dbt_num = 0;
+ mmd->num_played = 0;
+ mmd->num_commands = 0;
+ mmd->events = 0;
+ mmd->num_connects = 0;
+ mmd->active_connections = 0;
+ strcpy(mmd->filename, "(none)");
+ mmd->audio_format = -1;
+ mmd->afs_status_flags = AFS_NEXT;
+ mmd->new_afs_status_flags = AFS_NEXT;
+ mmd->sender_cmd_data.cmd_num = -1;
+
+ mmd_semid = semget(42, 1, IPC_CREAT | 0666);
+ if (mmd_semid == -1) {
+ PARA_EMERG_LOG("%s", "semget failed\n");
+ exit(EXIT_FAILURE);
+ }
+}
+
+static void do_semop(struct sembuf *sops, int num)
+{
+ if (semop(mmd_semid, sops, num) >= 0)
+ return;
+ PARA_WARNING_LOG("semop failed (%s), retrying\n", strerror(errno));
+ while (semop(mmd_semid, sops, num) < 0)
+ ; /* nothing */
+}
+
+/**
+ * lock the shared memory area containing the mmd struct
+ *
+ * \sa semop(2), struct misc_meta_data
+ */
+void mmd_lock(void)
+{
+ struct sembuf sops[2] = {
+ {
+ .sem_num = 0,
+ .sem_op = 0,
+ .sem_flg = SEM_UNDO
+ },
+ {
+ .sem_num = 0,
+ .sem_op = 1,
+ .sem_flg = SEM_UNDO
+ }
+ };
+ do_semop(sops, 2);
+}
+
+/**
+ * unlock the shared memory area containing the mmd struct
+ *
+ * \sa semop(2), struct misc_meta_data
+ */
+void mmd_unlock(void)
+{
+ struct sembuf sops[1] = {
+ {
+ .sem_num = 0,
+ .sem_op = -1,
+ .sem_flg = SEM_UNDO
+ },
+ };
+ do_semop(sops, 1);
+}
+
+static void parse_config(int override)
+{
+ char *home = para_homedir();
+ struct stat statbuf;
+ int ret;
+ char *cf;
+
+ if (conf.config_file_given)
+ cf = conf.config_file_arg;
+ else
+ cf = make_message("%s/.paraslash/server.conf", home);
+ free(user_list);
+ if (!conf.user_list_given)
+ user_list = make_message("%s/.paraslash/server.users", home);
+ else
+ user_list = para_strdup(conf.user_list_arg);
+ ret = stat(cf, &statbuf);
+ if (ret && conf.config_file_given) {
+ ret = -1;
+ PARA_EMERG_LOG("can not stat config file %s\n", cf);
+ goto out;
+ }
+ if (!ret) {
+ int tmp = conf.daemon_given;
+ cmdline_parser_configfile(cf, &conf, override, 0, 0);
+ conf.daemon_given = tmp;
+ }
+ /* logfile */
+ if (!conf.logfile_given && conf.daemon_given) {
+ ret = -1;
+ PARA_EMERG_LOG("%s", "daemon, but no log file\n");
+ goto out;
+ }
+ if (conf.logfile_given)
+ logfile = open_log(conf.logfile_arg);
+ ret = 1;
+out:
+ free(cf);
+ free(home);
+ if (ret > 0)
+ return;
+ free(user_list);
+ user_list = NULL;
+ exit(EXIT_FAILURE);
+}
+
+static void setup_signal_handling(void)
+{
+ int ret = 0;
+
+ signal_pipe = para_signal_init();
+// fcntl(signal_pipe, F_SETFL, O_NONBLOCK);
+ PARA_NOTICE_LOG("%s", "setting up signal handlers\n");
+ ret += para_install_sighandler(SIGINT);
+ ret += para_install_sighandler(SIGTERM);
+ ret += para_install_sighandler(SIGHUP);
+ ret += para_install_sighandler(SIGCHLD);
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGUSR1, SIG_IGN);
+ if (ret != 4) {
+ PARA_EMERG_LOG("%s", "could not install signal handlers\n");
+ exit(EXIT_FAILURE);
+ }
+}
+
+static void init_dbtool(void)
+{
+ int i;
+
+ mmd->dbt_change = -1; /* no change nec., set to new dbt num by com_cdt */
+ if (!dblist[1].name)
+ goto dopey;
+ if (conf.dbtool_given) {
+ for (i = 0; dblist[i].name; i++) {
+ if (strcmp(dblist[i].name, conf.dbtool_arg))
+ continue;
+ PARA_NOTICE_LOG("initializing %s database tool\n",
+ dblist[i].name);
+ if (dblist[i].init(&dblist[i]) < 0) {
+ PARA_WARNING_LOG("init %s failed",
+ dblist[i].name);
+ goto dopey;
+ }
+ mmd->dbt_num = i;
+ return;
+ }
+ PARA_WARNING_LOG("%s", "no such dbtool, switching to dopey\n");
+ goto dopey;
+ }
+ /* use the first dbtool that works
+ * (assuming that dopey always works)
+ */
+ for (i = 1; dblist[i].name; i++) {
+ int ret = dblist[i].init(&dblist[i]);
+ if (ret >= 0) {
+ PARA_INFO_LOG("initialized %s\n", dblist[i].name);
+ mmd->dbt_num = i;
+ return;
+ }
+ PARA_CRIT_LOG("%s init failed: %s\n", dblist[i].name,
+ PARA_STRERROR(-ret));
+ }
+dopey:
+ mmd->dbt_num = 0;
+ dblist[0].init(&dblist[0]); /* always successful */
+}
+
+static unsigned init_network(void)
+{
+ int sockfd = init_tcp_socket(conf.port_arg);
+
+ if (sockfd < 0)
+ exit(EXIT_FAILURE);
+ return sockfd;
+}
+
+static void init_random_seed(void)
+{
+ int fd, ret = -1, len = sizeof(unsigned int);
+ unsigned int seed;
+
+ fd = open("/dev/random", O_RDONLY);
+ if (fd < 0)
+ goto out;
+ ret = -2;
+ if (read(fd, &seed, len) != len)
+ goto out;
+ srandom(seed);
+ ret = 1;
+out:
+ if (fd >= 0)
+ close(fd);
+ if (ret > 0)
+ return;
+ PARA_EMERG_LOG("can not seed pseudo random generator (ret = %d)\n",
+ ret);
+ exit(EXIT_FAILURE);
+}
+
+static unsigned do_inits(int argc, char **argv)
+{
+ /* connector's address information */
+ int sockfd;
+
+ init_random_seed();
+ /* parse command line options */
+ cmdline_parser(argc, argv, &conf);
+ para_drop_privileges(conf.user_arg);
+ /* parse config file, open log and set defaults */
+ parse_config(0);
+ log_welcome("para_server", conf.loglevel_arg);
+ shm_init(); /* init mmd struct */
+ server_uptime(UPTIME_SET); /* reset server uptime */
+ /* become daemon */
+ if (conf.daemon_given)
+ daemon_init();
+ init_dbtool();
+ PARA_NOTICE_LOG("%s", "initializing audio file sender\n");
+ /* audio file sender */
+ afs_init();
+ mmd->server_pid = getpid();
+ setup_signal_handling();
+ mmd_lock();
+ /* init network socket */
+ PARA_NOTICE_LOG("%s", "initializing tcp command socket\n");
+ sockfd = init_network();
+ if (conf.autoplay_given) {
+ mmd->afs_status_flags |= AFS_PLAYING;
+ mmd->new_afs_status_flags |= AFS_PLAYING;
+ }
+ PARA_NOTICE_LOG("%s", "init complete\n");
+ return sockfd;
+}
+
+static void handle_dbt_change(void)
+{
+ int ret, old = mmd->dbt_num, new = mmd->dbt_change;
+
+ dblist[old].shutdown();
+ ret = dblist[new].init(&dblist[new]);
+ mmd->dbt_change = -1; /* reset */
+ if (ret >= 0) {
+ mmd->dbt_num = new;
+ return;
+ }
+ /* init failed */
+ PARA_ERROR_LOG("%s -- switching to dopey\n", PARA_STRERROR(-ret));
+ dblist[0].init(&dblist[0]);
+ mmd->dbt_num = 0;
+}
+
+/*
+ * called when server gets SIGHUP or when client invokes hup command.
+ */
+static void handle_sighup(void)
+{
+ PARA_NOTICE_LOG("%s", "SIGHUP\n");
+ close_log(logfile); /* gets reopened if necessary by parse_config */
+ logfile = NULL;
+ parse_config(1); /* reopens log */
+ mmd->dbt_change = mmd->dbt_num; /* do not change dbtool */
+ handle_dbt_change(); /* force reloading dbtool */
+}
+
+static void status_refresh(void)
+{
+ static int prev_uptime = -1, prev_events = -1;
+ int uptime = server_uptime(UPTIME_GET), ret = 1;
+
+ if (prev_events != mmd->events)
+ goto out;
+ if (mmd->new_afs_status_flags != mmd->afs_status_flags)
+ goto out;
+ if (uptime / 60 != prev_uptime / 60)
+ goto out;
+ ret = 0;
+out:
+ prev_uptime = uptime;
+ prev_events = mmd->events;
+ mmd->afs_status_flags = mmd->new_afs_status_flags;
+ if (ret) {
+ PARA_DEBUG_LOG("%d events, forcing status update, af = %d\n",
+ mmd->events, mmd->audio_format);
+ killpg(0, SIGUSR1);
+ }
+}
+
+/*
+ * MAIN
+ */
+int main(int argc, char *argv[])
+{
+ /* listen on sock_fd, new connection on new_fd */
+ int sockfd, new_fd;
+ struct sockaddr_in their_addr;
+ int err, i, max_fileno, ret;
+ pid_t chld_pid;
+ fd_set rfds, wfds;
+ struct timeval *timeout;
+
+ valid_fd_012();
+ sockfd = do_inits(argc, argv);
+repeat:
+ /* check socket and signal pipe in any case */
+ FD_ZERO(&rfds);
+ FD_ZERO(&wfds);
+ FD_SET(sockfd, &rfds);
+ max_fileno = sockfd;
+ FD_SET(signal_pipe, &rfds);
+ max_fileno = MAX(max_fileno, signal_pipe);
+
+ timeout = afs_preselect();
+ status_refresh();
+ for (i = 0; senders[i].name; i++) {
+ if (senders[i].status != SENDER_ON)
+ continue;
+ if (!senders[i].pre_select)
+ continue;
+ senders[i].pre_select(mmd->audio_format >= 0?
+ &afl[mmd->audio_format] : NULL,
+ &max_fileno,
+ &rfds, &wfds);
+ }
+ mmd_unlock();
+// PARA_DEBUG_LOG("%s: select (max = %i)\n", __func__, max_fileno);
+ ret = select(max_fileno + 1, &rfds, &wfds, NULL, timeout);
+ err = errno;
+ //PARA_DEBUG_LOG("%s: select returned %i\n", __func__, ret);
+ mmd_lock();
+ if (mmd->dbt_change >= 0)
+ handle_dbt_change();
+ if (ret < 0 && err == EINTR)
+ goto repeat;
+ if (ret < 0) {
+ PARA_CRIT_LOG("select error (%s)\n", strerror(err));
+ goto repeat;
+ }
+ for (i = 0; senders[i].name; i++) {
+ if (senders[i].status != SENDER_ON)
+ continue;
+ if (!senders[i].post_select)
+ continue;
+ senders[i].post_select(mmd->audio_format >= 0?
+ &afl[mmd->audio_format] : NULL,
+ &rfds, &wfds);
+ }
+ if (!ret) {
+ afs_send_chunk();
+ status_refresh();
+ }
+ if (FD_ISSET(signal_pipe, &rfds)) {
+ int sig;
+ sig = para_next_signal();
+ switch (sig) {
+ case SIGHUP:
+ handle_sighup();
+ break;
+ case SIGCHLD:
+ para_reap_children();
+ break;
+ /* die on sigint/sigterm. Kill all children too. */
+ case SIGINT:
+ case SIGTERM:
+ PARA_EMERG_LOG("terminating on signal %d\n", sig);
+ kill(0, SIGTERM);
+ exit(EXIT_FAILURE);
+ }
+ }
+ if (mmd->sender_cmd_data.cmd_num >= 0) {
+ int num = mmd->sender_cmd_data.cmd_num,
+ s = mmd->sender_cmd_data.sender_num;
+
+ if (senders[s].client_cmds[num])
+ senders[s].client_cmds[num](&mmd->sender_cmd_data);
+ mmd->sender_cmd_data.cmd_num = -1;
+ }
+ if (!FD_ISSET(sockfd, &rfds))
+ goto repeat;
+
+ new_fd = para_accept(sockfd, &their_addr, sizeof(struct sockaddr_in));
+ if (new_fd < 0)
+ goto repeat;
+ PARA_INFO_LOG("got connection from %s, forking\n",
+ inet_ntoa(their_addr.sin_addr));
+ mmd->num_connects++;
+ mmd->active_connections++;
+ random();
+ chld_pid = fork();
+ if (chld_pid < 0) {
+ PARA_CRIT_LOG("%s", "fork failed\n");
+ goto repeat;
+ }
+ if (chld_pid) {
+ close(new_fd);
+ /* parent keeps accepting connections */
+ goto repeat;
+ }
+ alarm(ALARM_TIMEOUT);
+ close_listed_fds();
+ close(sockfd); /* child doesn't need the listener */
+ /*
+ * put info on who we are serving into argv[0] to make
+ * client ip visible in top/ps
+ */
+ for (i = argc - 1; i >= 0; i--)
+ memset(argv[i], 0, strlen(argv[i]));
+ sprintf(argv[0], "para_server (serving %s)",
+ inet_ntoa(their_addr.sin_addr));
+ return handle_connect(new_fd, &their_addr);
+}
--- /dev/null
+section "General options"
+option "loglevel" l "set loglevel (0-6)" int typestr="level" default="4" no
+option "port" p "port to listen on" int typestr="portnumber" default="2990" no
+option "daemon" d "run as background daemon" flag off
+option "user" u "run as user 'name'. para_server does not need any special privileges. If started as root (EUID == 0) this option must be given at the command line (not in the configuration file) so that para_server can drop the root privileges right after parsing the command line options, but before parsing the configuration file. In this case, real/effective/saved UID are all set to the UID of 'username'. As the configuration file is read afterwards, those options that have a default value depending on the UID (e.g. the home directory for the configuration file) are computed by using the uid of 'username'. This option has no effect if para_server ist started as a non-root user (i.e. EUID != 0)" string typestr="name" no
+
+section "Configuration files"
+option "logfile" L "(default=stdout/stderr)" string typestr="filename" no
+option "config_file" c "(default='~/.paraslash/server.conf'" string typestr="filename" no
+option "user_list" - "(default='~/.paraslash/server.users')" string typestr="filename" no
+
+section "Options concerning the audio file sender"
+option "autoplay" a "start playing on startup" flag off
+option "announce_time" A "Delay betweeen announcing the stream and sending data" int typestr="milliseconds" default="300" no
+option "dbtool" D "(default=first available that works)" string typestr="name_of_dbtool" no
+
+section "Mysql database tool options"
+option "mysql_host" - "mysql server" string default="localhost" no
+option "mysql_port" - "where mysql is listening" int default="3306" no
+option "mysql_user" - "default value: username from /etc/passwd" string no
+option "mysql_passwd" - "(required)" string no
+option "mysql_database" - "name of mysql database" string default="paraslash" no
+option "mysql_audio_file_dir" - "dir to search for audio files (required)" string no
+option "mysql_default_score" - "scoring rule to use if stream definition does not contain explicit score definition" string default="(LASTPLAYED() / 1440 - 1000 / (LASTPLAYED() + 1) - sqrt(NUMPLAYED()))" no
+
+
+
+section "Dopey database tool options"
+option "dopey_dir" - "dir to search for files to be streamed" string default="/home/music" no
+
+section "Http sender options"
+option "http_port" - "tcp port for http streaming" int typestr="portnumber" default="8000" no
+option "http_default_deny" - "deny connections from hosts which are not explicitly allowed" flag off
+option "http_access" - "Add given host/network to access control list (whitelist if http_default_deny was given, blacklist otherwise) before opening the tcp port. This option can be given multiple times. Example: '192.168.0.0/24' whitelists/blacklists the 256 hosts 192.168.0.x" string typestr="a.b.c.d/n" no multiple
+option "http_no_autostart" - "do not open tcp port on server startup" flag off
+option "http_max_clients" - "maximal simultaneous connections, non-positive value means unlimited" int typestr="number" default="-1" no
+
+section "Ortp sender options"
+option "ortp_target" - "Add given host/port to the list of targets. This option can be given multiple times. Example: '224.0.1.38:1500' instructs the ortp sender to send to udp port 1500 on host 224.0.1.38 (unassigned ip in the Local Network Control Block 224.0.0/24). This is useful for LAN-streaming." string typestr="a.b.c.d:p" no multiple
+option "ortp_no_autostart" - "do not start to send automatically" flag off
+option "ortp_default_port" - "default udp port if not specified" int typestr="portnumber" default="1500" no
+option "ortp_header_interval" H "time between extra header sends" int typestr="milliseconds" default="2000" no
+
--- /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 server.h common server data structures */
+
+#include "para.h"
+
+
+/** size of the dbinfo and audio_file info strings of struct misc_meta_data */
+#define MMD_INFO_SIZE 16384
+
+/**
+ * permission flags that can be set individually for any server command
+ *
+ * - DB_READ: command reads from the database
+ * - DB_WRITE: command changes the contents of the database
+ * - AFS_READ: command reads information about the current audio stream
+ * - AFS_WRITE: command changes the current audio stream
+ */
+enum {DB_READ = 1, DB_WRITE = 2, AFS_READ = 4, AFS_WRITE = 8};
+
+/**
+ * data needed to authenticate the user
+ */
+struct user{
+/** the username */
+ char name[MAXLINE];
+/** full path to the public RSA key */
+ char pubkey_file[_POSIX_PATH_MAX];
+/** the privileges of this user */
+ unsigned int perms;
+};
+
+/**
+ * defines one command of para_server
+ */
+struct server_command {
+/** the name of the command */
+ char *name;
+/** pointer to the function that handles the command */
+ int (*handler)(int, int, char **);
+/** the privileges a user must have to execute this command */
+ unsigned int perms;
+/** one-line description of the command */
+ char *description;
+/** summary of the command line options */
+ char *synopsis;
+/** the long help text */
+ char *help;
+};
+
+/** holds the arguments for the para_server's sender command */
+struct sender_command_data{
+/** greater than 0 indicates that a sender cmd is already queued */
+ int cmd_num;
+/** the number of the sender in question */
+ int sender_num;
+/** used for the allow/deny/add/remove subcommands */
+ struct in_addr addr;
+/** used for allow/deny */
+ int netmask;
+/** the portnumber for add/remove */
+ int port;
+};
+
+/**
+ * used for parent-child communication
+ *
+ * There's only one struct of this type which lives in shared memory
+ * for communication between the server instances. Access to this
+ * area is serialized via mmd_lock() and mmd_unlock(). There are two
+ * reasons for a variable to be included here:
+ *
+ * - At least one command (i.e. child of the server) must be able to
+ * change its value.
+ *
+ * or
+ *
+ * - The contents are listed in the stat command and have to be up to
+ * date.
+ */
+struct misc_meta_data{
+/** the size of the current audio file in bytes */
+ long unsigned int size;
+/** the full path of the current audio file */
+ char filename[_POSIX_PATH_MAX];
+/** the last modification file of the current audio file */
+ time_t mtime;
+/* the number of the current audio format */
+ int audio_format;
+/** the "old" status flags -- commands may only read them */
+ unsigned int afs_status_flags;
+/** the new status flags -- commands may set them **/
+ unsigned int new_afs_status_flags;
+/** the number of data chunks sent for the current audio file */
+ long unsigned chunks_sent;
+/** the number of chunks this audio file contains */
+ long unsigned chunks_total;
+/** set by the jmp/ff commands to the new position in chunks */
+ long unsigned repos_request;
+/** the number of the chunk currently sent out*/
+ long unsigned current_chunk;
+/** the milliseconds that have been skipped of the current audio file */
+ long offset;
+/** the length of the audio file in seconds */
+ int seconds_total;
+/** the time para_server started to stream */
+ struct timeval stream_start;
+/** a string that gets filled in by the audio format handler */
+ char audio_file_info[MMD_INFO_SIZE];
+/** the event counter
+ *
+ * commands may increase this to force a status update to be sent to all
+ * connected clients
+*/
+ unsigned int events;
+/** the number of audio files already sent */
+ unsigned int num_played;
+/** the number of executed commands */
+ unsigned int num_commands;
+/** the number of connections para_server received so far */
+ unsigned int num_connects;
+/** the number of connections currently active */
+ unsigned int active_connections;
+/** the process id of para_server */
+ pid_t server_pid;
+/** a string that gets filled in by the current database tool */
+ char dbinfo[MMD_INFO_SIZE];
+/** the number if the current database tool */
+ int dbt_num;
+/** commands set this to non-zero to request a database tool change */
+ int dbt_change;
+/* used by the sender command */
+ struct sender_command_data sender_cmd_data;
+};
+
+
+int handle_connect(int fd, struct sockaddr_in *addr);
+void mmd_unlock(void);
+void mmd_lock(void);
--- /dev/null
+/*
+ * Copyright (C) 2004-2006 Andre Noll <maan@systemlinux.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+ */
+/** \file signal.c signal handling functions */
+
+#include "para.h"
+#include "error.h"
+static int signal_pipe[2];
+
+/**
+ * initialize the paraslash signal subsystem
+ *
+ * This function creates a pipe, the signal pipe, to deliver pending signals to
+ * the application. It should be called during the application's startup part,
+ * followed by subsequent calls to para_install_sighandler() for each signal
+ * that should be caught.
+ *
+ * para_signal_init() installs a generic signal handler which is used for all
+ * signals simultaneously. When a signal arrives, this generic signal handler
+ * writes the corresponding signal number to the signal pipe so that the
+ * application can test for pending signals simply by checking the signal pipe
+ * for reading, e.g. by using the select(2) system call.
+ *
+ * \return This function either succeeds or calls exit(2) to terminate
+ * the current process. On success, the file descriptor of the signal pipe is
+ * returned.
+ */
+int para_signal_init(void)
+{
+ if (!pipe(signal_pipe))
+ return signal_pipe[0];
+ PARA_EMERG_LOG("%s", "pipe error: Can not setup signal pipe");
+ exit(EXIT_FAILURE);
+}
+
+/*
+ * just write one integer to signal pipe
+ */
+static void generic_signal_handler(int s)
+{
+ write(signal_pipe[1], &s, sizeof(int));
+ //fprintf(stderr, "got sig %i, write returned %d\n", s, ret);
+}
+
+/**
+ * reap one child
+ *
+ * call waitpid() and print a log message containing the pid
+ * and the cause of the child's death.
+ *
+ * \return Like \p waitpid(), this function returns the process ID of the
+ * terminated child; on error, \p -E_WAITPID is returned.
+ * \sa waitpid(2)
+ */
+pid_t para_reap_child(void)
+{
+ int status;
+ pid_t pid = waitpid(-1, &status, WNOHANG);
+
+ if (pid <= 0) {
+ if (pid < 0)
+ pid = -E_WAITPID;
+ return 0;
+ }
+ if (WIFEXITED(status))
+ PARA_DEBUG_LOG("child %i exited. Exit status: %i\n", pid,
+ WEXITSTATUS(status));
+ else if (WIFSIGNALED(status))
+ PARA_DEBUG_LOG("child %i was killed by signal %i\n", pid,
+ WTERMSIG(status));
+ else
+ PARA_WARNING_LOG("child %i terminated abormally\n", pid);
+ return pid;
+}
+
+/**
+ * paraslash's zombie killer
+ *
+ * It just calls \p para_reap_child() until there are no more children left to
+ * reap.
+ */
+void para_reap_children(void)
+{
+ while (para_reap_child() > 0)
+ ; /* nothing */
+}
+
+/**
+ * wrapper around signal(2)
+ * \param sig the number of the signal to catch
+ *
+ * This installs the generic signal handler for the given signal.
+ * \return This function returns 1 on success and \p -E_SIGNAL_SIG_ERR on errors.
+ * \sa signal(2)
+ */
+int para_install_sighandler(int sig)
+{
+ PARA_DEBUG_LOG("catching signal %d\n", sig);
+ return signal(sig, &generic_signal_handler) == SIG_ERR? -E_SIGNAL_SIG_ERR : 1;
+}
+
+/**
+ * return number of next pending signal
+ *
+ * This should be called if the fd for the signal pipe is ready for reading.
+ *
+ * \return On success, the number of the received signal is returned. \p
+ * -E_SIGNAL_READ is returned if a read error occured while reading the signal
+ * pipe. If the read was interrupted by another signal the function returns 0.
+ */
+int para_next_signal(void)
+{
+ int s;
+ ssize_t r;
+
+ if ((r = read(signal_pipe[0], &s, sizeof(s)) == sizeof(s)) > 0) {
+ PARA_DEBUG_LOG("next signal: %d\n", s);
+ return s;
+ }
+ return r < 0 && (errno != EAGAIN)? 0 : -E_SIGNAL_READ;
+}
--- /dev/null
+##Sketch 1 2
+document()
+layout('A4',0)
+layer('Layer 1',1,1,0,0,(0,0,0))
+G()
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('SQL query',(3.06152e-16,1,-1,3.06152e-16,73.5128,678.229))
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(76.5508,667.712,0)
+bs(76.5508,750.57,0)
+G_()
+G()
+lw(1)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(205.995,666.16,0)
+bs(205.995,744.222,0)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('open file',(1.19433e-15,1,-1,1.19433e-15,202.957,680.502))
+G_()
+G()
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('mp3 data',(-1.83691e-16,-1,1,-1.83691e-16,241.524,730.112))
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(238.486,739.793,0)
+bs(238.486,668.714,0)
+G_()
+G()
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('fork & exec',(1.19433e-15,1,-1,1.19433e-15,215.967,398.919))
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(219.005,388.674,0)
+bs(219.005,476.027,0)
+G_()
+G()
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(238.331,474.579,0)
+bs(238.331,392.999,0)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('server status',(-1.83691e-16,-1,1,-1.83691e-16,241.369,468.586))
+G_()
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(145.824,181.165,0)
+bs(228.568,181.165,0)
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(228.568,161.165,0)
+bs(145.824,161.165,0)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('connect local socket',(1.19433e-15,1,-1,1.19433e-15,272.988,207.639))
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(307.4,339.815,0)
+bs(307.4,200,0)
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(280,200,0)
+bs(280,339.815,0)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('server & audiod status',(-1.83691e-16,-1,1,-1.83691e-16,310.438,332.705))
+G()
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(99.2303,144.289,0)
+bs(99.2303,73.1918,0)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('s&a status',(-1.83691e-16,-1,1,-1.83691e-16,102.268,137.713))
+G_()
+G()
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(61.1648,150.482,0)
+bs(61.1647,71.2323,0)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('cmd output',(-1.83691e-16,-1,1,-1.83691e-16,64.2028,142.553))
+G_()
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(498.256,150.683,0)
+bs(498.256,72.1559,0)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('pcm data',(-1.83691e-16,-1,1,-1.83691e-16,481.11,134.249))
+lw(0.992126)
+ld((0.10000000000000001, 1.0))
+b()
+bs(24.7094,792.443,0)
+bs(310.724,792.443,0)
+bs(310.421,682.725,0)
+bs(561.98,682.585,0)
+bs(561.98,609.679,0)
+bs(24.7094,609.679,0)
+bs(24.7094,792.443,0)
+bC()
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Italic')
+txt('server',(523.826,666.279))
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Italic')
+txt('client',(334.671,487.931))
+G()
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(105.06,750.094,0)
+bs(105.06,667.236,0)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('mysql result',(-1.83691e-16,-1,1,-1.83691e-16,108.098,743.084))
+G_()
+G()
+lw(1)
+r(78.7206,0,0,-28.2587,185.03,786.713)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(24)
+txt('storage',(189.734,766.992))
+G_()
+G()
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(24)
+txt('mysqld',(35.977,766.992))
+lw(1)
+r(75.6929,0,0,-28.2587,33.4706,786.713)
+G_()
+G()
+lw(1)
+r(119.09,0,0,-28.2587,39.0214,375.322)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(24)
+txt('para_client',(45.2504,355.601))
+G_()
+lw(1)
+r(119.09,0,0,-28.2587,180.229,509.717)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(24)
+txt('para_client',(186.458,489.996))
+G()
+lw(1)
+r(143.312,0,0,-28.2587,413.631,59.6823)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(24)
+txt('sound_device',(419.299,39.9609))
+G_()
+G()
+lw(1)
+r(94.8684,0,0,-28.2587,34.9844,58.6731)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(24)
+txt('terminal',(42.4226,38.9517))
+G_()
+G()
+lw(1)
+r(131.201,0,0,-28.2587,237.937,189.4)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(24)
+txt('para_audioc',(244.894,169.678))
+G_()
+G()
+lw(1)
+r(94.8684,0,0,-28.2587,34.9844,189.4)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(24)
+txt('para_gui',(40.4306,169.678))
+G_()
+lw(1)
+r(114.044,0,0,-28.2587,441.89,188.865)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(24)
+txt('para_play',(448.5,169.679))
+G()
+G()
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(216.8,522.254,0)
+bs(216.8,603.141,0)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('connect tcp',(1.19433e-15,1,-1,1.19433e-15,213.762,530.624))
+G_()
+G()
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('server status',(-1.83691e-16,-1,1,-1.83691e-16,249.561,597.495))
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(246.523,603.141,0)
+bs(246.523,522.254,0)
+G_()
+G_()
+fp((0.9,0.9,0.9))
+lw(1)
+r(177.037,0,0,-89.6831,384.699,788.574)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Bold')
+Fs(36)
+txt('paraslash',(399.201,755.055))
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Bold')
+Fs(36)
+txt('0.2.9',(437.217,715.454))
+G()
+lw(1)
+r(517.739,0,0,-28.2587,33.4706,653.823)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(24)
+txt('para_server',(236.372,634.102))
+G_()
+G()
+lw(1)
+r(360.298,0,0,-28.2587,199.586,375.322)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(24)
+txt('para_audiod',(320.419,355.601))
+G_()
+G()
+G()
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(514.857,324.515,0)
+bs(514.857,209.912,0)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('wav data',(-1.83691e-16,-1,1,-1.83691e-16,517.895,292.288))
+G_()
+G()
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('fork & exec',(-1.83691e-16,-1,1,-1.83691e-16,484.591,300.646))
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(481.553,324.515,0)
+bs(481.553,209.912,0)
+G_()
+G_()
+G()
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('fork & exec',(153.764,182.009))
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('s&a status',(158.223,162.009))
+G_()
+G()
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('fork & exec',(1.19433e-15,1,-1,1.19433e-15,70.3615,232.519))
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(73.3994,201.263,0)
+bs(73.3995,330.639,0)
+G_()
+G()
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(103.54,330.639,0)
+bs(103.539,201.263,0)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('cmd output',(-1.83691e-16,-1,1,-1.83691e-16,106.577,297.647))
+G_()
+G()
+lw(1)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(505.839,595.876,0)
+bs(505.839,400.592,0)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('mp3 data ',(-1.83691e-16,-1,1,-1.83691e-16,508.877,525.842))
+G_()
+G()
+G()
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('connect tcp, send cmd',(2.57235e-15,1,-1,2.57235e-15,64.0626,431.071))
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(67.1006,390.148,0)
+bs(67.1006,596.411,0)
+G_()
+G()
+lw(0.992126)
+la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0))
+b()
+bs(101.501,596.411,0)
+bs(101.501,390.148,0)
+fp((0,0,0))
+le()
+lw(1)
+Fn('Times-Roman')
+Fs(14)
+txt('cmd output',(-1.83691e-16,-1,1,-1.83691e-16,104.539,524.976))
+G_()
+G_()
+lw(0.992126)
+ld((0.10000000000000001, 1.0))
+b()
+bs(29.1803,383.603,0)
+bs(164.433,383.603,0)
+bs(164.433,513.811,0)
+bs(396.545,513.811,0)
+bs(396.545,383.568,0)
+bs(570.132,383.568,0)
+bs(570.132,26.3198,0)
+bs(27.1619,26.3198,0)
+bs(27.1619,382.595,0)
+guidelayer('Guide Lines',1,0,0,1,(0,0,1))
+guide(-307.905,0)
+grid((0,0,20,20),0,(0,0,1),'Grid')
--- /dev/null
+/*
+ * Copyright (C) 2004-2006 Andre Noll <maan@systemlinux.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+ */
+
+/** \file slider.c libzmw-based stream creator for paraslash */
+
+#include "para.h"
+#include "zmw/zmw.h"
+#include "math.h"
+#include "string.h"
+
+#define WIDTH 300
+#define SLIDER_RATIO 6
+#define VAL_2_SL_VAL(v) (v) * (SLIDER_RATIO - 1.0) / SLIDER_RATIO
+#define SL_VAL_2_VAL(v) (v) * SLIDER_RATIO / (SLIDER_RATIO - 1.0)
+#define VAL_2_SCORE(v) (v) > 0.5? 1 / (1.03 - (v)) / (1.03 - (v)) :\
+ - 1 / ((v) + 0.03) / ((v) + 0.03)
+
+#define RGB(R,G,B) (((R)<<12) + ((G)<<6) + (B))
+
+#define EPSILON 0.001
+#define LASTPLAYED_FORMULA(v) \
+ 1 / (pow((v), 10) + EPSILON * EPSILON) - 1 / (1 + EPSILON) + EPSILON
+
+#define NUMPLAYED_FORMULA(v)\
+ 10 * (v) + (v) / (1 - (v) * (1 - EPSILON))
+
+
+static int argc;
+static char **argv;
+char *streamname = NULL;
+static Zmw_Float_0_1 *slider_vals, lastplayed_val, numplayed_val;
+
+#if 0
+static void boxed_text(const char *text, int i)
+{
+ ZMW(zmw_decorator(Zmw_Decorator_Border_Embossed))
+ {
+ if (i == 2)
+ {
+ zmw_horizontal_expand(Zmw_False) ;
+ zmw_vertical_expand(Zmw_False) ;
+ }
+ zmw_label(text) ;
+ }
+}
+#endif
+
+void para_log(int ll, char* fmt,...) /* no logging */
+{
+}
+
+static void rst(void)
+{
+ int i;
+ for (i = 1; argv[i]; i++)
+ slider_vals[i] = VAL_2_SL_VAL(.5);
+ if (streamname)
+ free(streamname);
+ streamname = para_strdup("slider");
+ //printf("rst: \n");
+}
+
+static char *get_value_filename(void)
+{
+ char *ret, *home =para_homedir();
+ ret = make_message("%s/.paraslash/slide.values", home);
+ free(home);
+ return ret;
+}
+
+static void load(void)
+{
+ char *tmp;
+ char att[999];
+ FILE *f;
+ int i;
+ Zmw_Float_0_1 val;
+
+ tmp = get_value_filename();
+ f = fopen(tmp, "r");
+ free(tmp);
+ if (!f)
+ return;
+ while (fscanf(f, "%900s %g", att, &val) == 2) {
+ if (!strcmp(att, "lastplayed")) {
+ lastplayed_val = val;
+ continue;
+ }
+ if (!strcmp(att, "numplayed")) {
+ numplayed_val = val;
+ continue;
+ }
+ for (i = 1; argv[i]; i++)
+ if (!strcmp(argv[i], att)) {
+ slider_vals[i] = val;
+ break;
+ }
+
+ }
+ fclose(f);
+}
+
+static void save(void)
+{
+ char *filename;
+ FILE *f;
+ int i;
+
+ filename = get_value_filename();
+ f = fopen(filename, "w");
+ free(filename);
+ if (!f)
+ return;
+ fprintf(f, "lastplayed %g\n", lastplayed_val);
+ fprintf(f, "numplayed %g\n", numplayed_val);
+ for (i = 1; argv[i]; i++)
+ fprintf(f, "%s %g\n", argv[i], slider_vals[i]);
+ fclose(f);
+}
+
+
+static void print_score(FILE *fd)
+{
+ int i;
+
+ for (i = 1; argv[i]; i++) {
+ if (SL_VAL_2_VAL(slider_vals[i]) > .99)
+ fprintf(fd, "deny: IS_N_SET(%s)\n", argv[i]);
+ if (slider_vals[i] < .01)
+ fprintf(fd, "deny: IS_SET(%s)\n", argv[i]);
+ }
+ fprintf(fd, "score: 0 ");
+ for (i = 1; argv[i]; i++) {
+ if (slider_vals[i] < .01)
+ continue;
+ fprintf(fd, "+ %i * IS_SET(%s) ",
+ (int) (VAL_2_SCORE(SL_VAL_2_VAL(slider_vals[i]))),
+ argv[i]
+ );
+ }
+ fprintf(fd, " + round((LASTPLAYED()/1440 - 1000 / (LASTPLAYED()/1440 + 1)) / %f"
+ " - %f * NUMPLAYED(), 2)\n",
+ LASTPLAYED_FORMULA(SL_VAL_2_VAL(lastplayed_val)),
+ NUMPLAYED_FORMULA(SL_VAL_2_VAL(numplayed_val)));
+}
+
+static void stradd(void)
+{
+ FILE *pipe;
+ char *cmd = make_message("para_client stradd %s", streamname);
+
+ pipe = popen(cmd, "w");
+ free(cmd);
+ if (!pipe)
+ return;
+ print_score(pipe);
+ pclose(pipe);
+}
+
+static void b_save(void)
+{
+ stradd();
+ save();
+}
+
+static void go(void)
+{
+ stradd();
+ system("para_client cs slider");
+ system("para_client stop");
+ system("para_client play");
+}
+
+static void ok()
+{
+ stradd();
+ system("para_client cs slider");
+ system("para_client play");
+ save();
+ zmw_main_quit(EXIT_SUCCESS);
+}
+
+static void make_buttons(void)
+{
+ /* Add a frame around the box */
+ ZMW(zmw_decorator(Zmw_Decorator_Border_Embossed)) {
+ /* the buttons */
+ ZMW(zmw_hbox()) {
+ zmw_button("save");
+ if (zmw_activated())
+ b_save();
+ zmw_button("ok");
+ if (zmw_activated())
+ ok();
+ zmw_button("go!");
+ if (zmw_activated())
+ go();
+ zmw_button("rst");
+ if (zmw_activated())
+ rst();
+// zmw_x(WIDTH);
+ zmw_button("abort");
+ if (zmw_activated())
+ zmw_main_quit(EXIT_FAILURE);
+ }
+ }
+}
+static void make_stream_input_field(void)
+{
+ static char *text1 = NULL;
+ static int cursor_pos = 1; // Must be static
+ static int text1_length ;
+
+ ZMW(zmw_vbox()) {
+ if ( text1 == NULL ) {
+ // The initial value must be malloced
+ text1 = strdup("slider") ;
+ text1_length = strlen(text1) ;
+ }
+ ZMW(zmw_fixed()) {
+ zmw_y(6);
+ zmw_x(0);
+ zmw_label("stream: ");
+ zmw_color(Zmw_Color_Foreground, RGB(63, 63, 63));
+ zmw_y(0);
+ zmw_x(100);
+ zmw_width(WIDTH);
+ zmw_entry_with_cursor(&streamname, &cursor_pos);
+ if (zmw_changed())
+ text1_length = strlen(text1);
+ }
+ }
+}
+
+/* the sliders, one for each member in argv */
+static void make_sliders(void)
+{
+ int i;
+ char *txt;
+
+ zmw_height(ZMW_VALUE_UNDEFINED);
+// zmw_horizontal_expand(Zmw_True);
+ ZMW(zmw_hbox()) {
+ ZMW(zmw_vbox()) {
+ zmw_color(Zmw_Color_Foreground, RGB(0, 63, 63)); /* font */
+ txt = make_message("lp: (%i%%)",
+ (int)(.5 + 100 * SL_VAL_2_VAL(lastplayed_val)));
+ zmw_label(txt);
+ free(txt);
+ txt = make_message("np: (%i%%)",
+ (int)(.5 + 100 * SL_VAL_2_VAL(numplayed_val)));
+ zmw_label(txt);
+ free(txt);
+ zmw_color(Zmw_Color_Foreground, RGB(63, 63, 0)); /* font */
+ for (i = 1; argv[i]; i++) {
+ txt = make_message("%s: (%i%%)", argv[i],
+ (int)(.5 + 100 * SL_VAL_2_VAL(slider_vals[i])));
+ zmw_label(txt);
+ free(txt);
+ }
+ }
+ ZMW(zmw_vbox()) {
+ zmw_width(200) ;
+ zmw_hscrollbar(&lastplayed_val, 1.0 / SLIDER_RATIO);
+ zmw_hscrollbar(&numplayed_val, 1.0 / SLIDER_RATIO);
+ for (i = 1; argv[i]; i++) {
+ zmw_hscrollbar(&slider_vals[i],
+ 1.0 / SLIDER_RATIO);
+ }
+ }
+ }
+}
+
+static void anchor(void)
+{
+
+ zmw_font_family("terminal");
+ zmw_font_size(14);
+ zmw_rgb(0.1, 0.1, 0.1);
+ zmw_color(Zmw_Color_Foreground, RGB(63, 63, 0)); /* font */
+ zmw_color(Zmw_Color_Background_Pushed, RGB(0, 10, 4)); /* slider bg */
+ zmw_color(Zmw_Color_Background_Poped, RGB(0, 42, 42)); /* button bg */
+ zmw_color(Zmw_Color_Border_Light, RGB(0, 7, 63)); /* slider/button border */
+
+ ZMW(zmw_window("para_slider")) {
+ ZMW(zmw_vbox()) {
+ make_buttons();
+ make_stream_input_field();
+ zmw_color(Zmw_Color_Foreground, RGB(0, 63, 63)); /* font */
+// make_special_sliders();
+ zmw_color(Zmw_Color_Foreground, RGB(63, 63, 0)); /* font */
+ make_sliders();
+ }
+ }
+}
+
+static void get_atts(void)
+{
+ char buf[256];
+ FILE *p = popen("para_client laa", "r");
+ argv = para_malloc(255 * sizeof(char *)); /* FIXME */
+
+ argv[0] = para_strdup("all_attributes");
+ argc = 1;
+ if (!p)
+ goto err_out;
+ while (fgets(buf, 255, p)) {
+ chop(buf);
+ argv[argc] = para_strdup(buf);
+ argc++;
+ }
+ pclose(p);
+ argv[argc] = NULL;
+ argc--;
+ if (argc > 1)
+ return; /* success */
+err_out:
+ if (argc > 1)
+ for (argc = 1; argv[argc]; argc++)
+ free(argv[argc]);
+ free(argv);
+ argc = 1;
+}
+
+int main(int the_argc, char *the_argv[])
+{
+ argc = the_argc;
+ argv = the_argv;
+ if (argc < 2) {
+ get_atts();
+ if (argc < 2)
+ return -1;
+ }
+ slider_vals = para_malloc((argc + 1) * sizeof(int*));
+ rst();
+ load();
+// printf("argc: %d\n", argc);
+// for (i = 1; argv[i]; i++)
+// printf("argv[%d]: %s\n", i, argv[i]);
+ zmw_init(&argc, &argv);
+ zmw_main(anchor);
+ return 0;
+}
--- /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 stat.c functions used for sending/receiving the status of para_server
+ * and para_audiod
+ */
+#include "para.h"
+#include "close_on_fork.h"
+#include "list.h"
+#include "error.h"
+#include "string.h"
+
+/** the maximal number of simultaneous connections */
+#define MAX_STAT_CLIENTS 50
+
+/**
+ * The structure associated with a connected client that sent the 'stat' command
+*
+ *
+ * A status client is identified by its file descriptor. para_audiod
+ * keeps a list of connected status clients.
+ */
+struct stat_client {
+/**
+ *
+ *
+ * the stat client's file descriptor
+ */
+int fd;
+/**
+ *
+ * its entry in the list of stat clients
+*/
+struct list_head node;
+};
+
+static struct list_head client_list;
+static int initialized;
+static int num_clients;
+
+/**
+ * the list of all status items sent by para_server/para_audiod
+ */
+const char *status_item_list[NUM_STAT_ITEMS] = {
+ [SI_STATUS_BAR] = "status_bar",
+ [SI_STATUS] = "status",
+ [SI_NUM_PLAYED] = "num_played",
+
+ [SI_MTIME] = "mtime",
+ [SI_LENGTH_MIN] = "length_min",
+ [SI_LENGTH_SEC] = "length_sec",
+
+ [SI_FILE_SIZE] = "file_size",
+ [SI_STATUS_FLAGS] = "status_flags",
+ [SI_FORMAT] = "format",
+
+ [SI_SCORE] = "score",
+ [SI_AUDIO_INFO1] = "audio_file_info1",
+ [SI_AUDIO_INFO2] = "audio_file_info2",
+
+ [SI_AUDIO_INFO3] = "audio_file_info3",
+ [SI_DBINFO1] = "dbinfo1",
+ [SI_DBINFO2] = "dbinfo2",
+
+ [SI_DBINFO3] = "dbinfo3",
+ [SI_DECODER_FLAGS] = "decoder_flags",
+ [SI_AUDIOD_STATUS] = "audiod_status",
+
+ [SI_PLAY_TIME] = "play_time",
+ [SI_UPTIME] = "uptime",
+ [SI_OFFSET] = "offset",
+
+ [SI_LENGTH] = "length",
+ [SI_STREAM_START] = "stream_start",
+ [SI_CURRENT_TIME] = "current_time",
+
+ [SI_AUDIOD_UPTIME] = "audiod_uptime",
+ [SI_DBTOOL] = "dbtool"
+};
+#define FOR_EACH_STAT_ITEM(i) for (i = 0; i < NUM_STAT_ITEMS; i++)
+
+static void dump_stat_client_list(void)
+{
+ struct stat_client *sc;
+
+ if (!initialized)
+ return;
+ list_for_each_entry(sc, &client_list, node)
+ PARA_INFO_LOG("stat client on fd %d\n", sc->fd);
+}
+/**
+ * add a status client to the list
+ *
+ * \return Positive value on success, or -E_TOO_MANY_CLIENTS if
+ * the number of connected clients exceeds #MAX_STAT_CLIENTS
+ */
+int stat_client_add(int fd)
+{
+ struct stat_client *new_client;
+
+ if (num_clients >= MAX_STAT_CLIENTS) {
+ PARA_ERROR_LOG("maximal number of stat clients (%d) exceeded\n",
+ MAX_STAT_CLIENTS);
+ return -E_TOO_MANY_CLIENTS;
+ }
+ if (!initialized) {
+ INIT_LIST_HEAD(&client_list);
+ initialized = 1;
+ }
+ PARA_INFO_LOG("adding client on fd %d\n", fd);
+ new_client = para_malloc(sizeof(struct stat_client));
+ new_client->fd = fd;
+ add_close_on_fork_list(fd);
+ list_add(&new_client->node, &client_list);
+ dump_stat_client_list();
+ num_clients++;
+ return 1;
+}
+/**
+ * write a message to all connected stat clients
+ *
+ * \param msg a \p NULL terminated buffer
+ */
+void stat_client_write(char *msg)
+{
+ struct stat_client *sc, *tmp;
+ char *buf;
+ ssize_t len;
+ struct timeval tv = {0 , 0};
+
+ if (!initialized)
+ return;
+ buf = make_message("%s\n", msg);
+ len = strlen(buf);
+ list_for_each_entry_safe(sc, tmp, &client_list, node) {
+ int fd = sc->fd, ret;
+ fd_set wfds;
+ FD_ZERO(&wfds);
+ FD_SET(fd, &wfds);
+// PARA_DEBUG_LOG("%s: p=%lx\n", __func__, (long)p);
+ do
+ ret = select(fd + 1, NULL, &wfds, NULL, &tv);
+ while (ret < 0 && errno == EINTR);
+ if (ret) {
+ ret = write(fd, buf, len);
+ PARA_DEBUG_LOG("dumped %s to fd %d, ret = %d\n", msg, fd, ret);
+ if (ret == len )
+ continue;
+ }
+ /* write error or fd not ready for writing */
+ close(fd);
+ del_close_on_fork_list(fd);
+ num_clients--;
+ PARA_INFO_LOG("deleting client on fd %d\n", fd);
+ list_del(&sc->node);
+ free(sc);
+ dump_stat_client_list();
+ }
+ free(buf);
+ PARA_DEBUG_LOG("%d client(s)\n", num_clients);
+}
+
+/**
+ * send empty status list
+ *
+ * Send to each connected client the full status item list
+ * with empty values.
+ */
+void dump_empty_status(void)
+{
+ int i;
+ char *empty_items = NULL;
+
+ if (!initialized)
+ return;
+ FOR_EACH_STAT_ITEM(i) {
+ char *tmp = make_message("%s%s:\n", empty_items? empty_items : "",
+ status_item_list[i]);
+ free(empty_items);
+ empty_items = tmp;
+ }
+// PARA_DEBUG_LOG("%s: empty items: %s\n", __func__, empty_items);
+ stat_client_write(empty_items);
+ free(empty_items);
+}
+
+/**
+ * check if line starts with known status item.
+ *
+ * \param line buffer containing the line
+ *
+ * \return If the beginning of \a line matches any paraslash status item and is
+ * followed by a colon, the number of that status item is returned. Otherwise,
+ * this function returns \p -E_UNKNOWN_STAT_ITEM.
+ */
+int stat_line_valid(const char *line)
+{
+ int i;
+ size_t line_len;
+
+ if (!line || !*line)
+ return -E_UNKNOWN_STAT_ITEM;
+ line_len = strlen(line);
+ for (i = 0; i < NUM_STAT_ITEMS; i++) {
+ const char *s = status_item_list[i];
+ size_t item_len = strlen(s);
+
+ if (line_len > item_len && line[item_len] == ':' &&
+ !strncmp(line, s, item_len))
+ return i;
+ }
+ return -E_UNKNOWN_STAT_ITEM;
+}
+
+/**
+ * call a custom function for each complete line
+ *
+ * \param buf the buffer containing data seperated by newlines
+ * \param n the number of bytes in \a buf
+ * \param line_handler the custom function
+ * \param num upper bound on calls to \a line_handler
+ *
+ * If \a line_handler is \p NULL, return number of complete lines in buf.
+ * Otherwise, call \a line_handler for each complete line, but no more than \a num
+ * times. If \a num is zero, there is no restriction on how often \a line_handler
+ * may be called. The rest of the buffer (last chunk containing incomplete line
+ * if \a num is zero) is moved to the beginning of the buffer.
+ *
+ * \return If line_handler is not NULL, this function returns the number
+ * of bytes not handled to \a line_handler.
+ */
+unsigned for_each_line(char *buf, int n, void (*line_handler)(char *), int num)
+{
+ char *start = buf, *end;
+ int i, num_lines = 0;
+
+ while (start < buf + n) {
+ char *next_null;
+ char *next_cr;
+
+ next_cr = memchr(start, '\n', buf + n - start);
+ next_null = memchr(start, '\0', buf + n - start);
+ if (!next_cr && !next_null)
+ break;
+ if (next_cr && next_null) {
+ end = next_cr < next_null? next_cr : next_null;
+ } else if (next_null) {
+ end = next_null;
+ } else
+ end = next_cr;
+ num_lines++;
+ if (line_handler) {
+ *end = '\0';
+ line_handler(start);
+ start = ++end;
+ if (num && num_lines >= num)
+ break;
+ } else
+ start = ++end;
+ }
+ if (!line_handler)
+ return num_lines;
+ i = buf + n - start;
+ if (i && i != n)
+ memmove(buf, start, i);
+ return i;
+}
+
--- /dev/null
+/*
+ * Copyright (C) 2004-2006 Andre Noll <maan@systemlinux.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+ */
+
+/** \file string.c memory allocation and string handling functions */
+
+#include <sys/time.h> /* gettimeofday */
+#include "para.h"
+#include <regex.h>
+#include <pwd.h>
+#include <sys/utsname.h> /* uname() */
+#include "error.h"
+#include "string.h"
+
+/**
+ * paraslash's version of realloc()
+ *
+ * \param p pointer to the memory block, may be NULL
+ * \param size desired new size
+ *
+ * A wrapper for realloc(3). It calls \p exit(\p EXIT_FAILURE) on errors,
+ * i.e. there is no need to check the return value in the caller.
+ * \sa realloc(3)
+ */
+__must_check __malloc void *para_realloc(void *p, size_t size)
+{
+ /*
+ * No need to check for NULL pointers: If p is NULL, the call
+ * to realloc is equivalent to malloc(size)
+ */
+ if (!(p = realloc(p, size))) {
+ PARA_EMERG_LOG("%s", "realloc failed, aborting\n");
+ exit(EXIT_FAILURE);
+ }
+ return p;
+}
+
+/**
+ * paraslash's version of malloc()
+ *
+ * \param size desired new size
+ *
+ * A wrapper for malloc(3) which exits on errors.
+ * \sa malloc(3)
+ */
+__must_check __malloc void *para_malloc(size_t size)
+{
+ void *p = malloc(size);
+
+ if (!p) {
+ PARA_EMERG_LOG("%s", "malloc failed, aborting\n");
+ exit(EXIT_FAILURE);
+ }
+ return p;
+}
+
+/**
+ * paraslash's version of calloc()
+ *
+ * \param size desired new size
+ *
+ * A wrapper for calloc(3) which exits on errors.
+ * \sa calloc(3)
+ */
+__must_check __malloc void *para_calloc(size_t size)
+{
+ void *ret = para_malloc(size);
+
+ memset(ret, 0, size);
+ return ret;
+}
+
+/**
+ * paraslash's version of strdup()
+ *
+ * \param s: string to be duplicated
+ *
+ * A wrapper for strdup(3). It calls exit(EXIT_FAILURE) on
+ * errors, i.e. there is no need to check the return value in the caller.
+ * Moreover, this wrapper checks for \a s being NULL and returns an empty
+ * string in this case.
+ *
+ * \sa strdup(3)
+ */
+__must_check __malloc char *para_strdup(const char *s)
+{
+ char *ret;
+
+ if ((ret = strdup(s? s: "")))
+ return ret;
+ PARA_EMERG_LOG("%s", "strdup failed, aborting\n");
+ exit(EXIT_FAILURE);
+}
+
+/**
+ * allocate a sufficiently large string and print into it
+ *
+ * \param fmt usual format string
+ *
+ * Produce output according to \a fmt. No artificial bound on the length of the
+ * resulting string is imposed. This function either returns a pointer to a
+ * string that must be freed by the caller or aborts without returning.
+ *
+ * \sa printf(3)
+ */
+__must_check __printf_1_2 __malloc char *make_message(const char *fmt, ...)
+{
+ char *msg;
+
+ PARA_VSPRINTF(fmt, msg);
+ return msg;
+}
+
+/**
+ * paraslash's version of strcat()
+ *
+ * \param a string to be appended to
+ * \param b string to append
+ *
+ * Append \a b to \a a. If \a a is NULL, return a copy of \a b, i.e.
+ * para_strcat(NULL, b) is equivalent to para_strdup(b). If \a b is NULL,
+ * return \a a without making a copy of \a a. Otherwise, construct the
+ * concatenation \a c, free \a a (but not \a b) and return \a c.
+ *
+ * \sa strcat(3)
+ */
+__must_check __malloc char *para_strcat(char *a, const char *b)
+{
+ char *tmp;
+
+ if (!a)
+ return para_strdup(b);
+ if (!b)
+ return a;
+ tmp = make_message("%s%s", a, b);
+ free(a);
+ return tmp;
+}
+
+/**
+ * paraslash's version of dirname()
+ *
+ * \param name pointer to The full path
+ *
+ * If \a name is \þ NULL or the empty string, return \p NULL, Otherwise, Make a
+ * copy of \a name and return its directory component. Caller is responsible to
+ * free the result.
+ */
+__must_check __malloc char *para_dirname(const char *name)
+{
+ char *p, *ret;
+
+ if (!name || !*name)
+ return NULL;
+ ret = para_strdup(name);
+ p = strrchr(ret, '/');
+ if (!p)
+ *ret = '\0';
+ else
+ *p = '\0';
+ return ret;
+}
+
+/**
+ * paraslash's version of basename()
+ *
+ * \param name Pointer to the full path
+ *
+ * If \a name is \p NULL or the empty string, return \p NULL, Otherwise, make a
+ * copy of \a name and return its filename component. Caller is responsible to
+ * free the result.
+ */
+__must_check __malloc char *para_basename(const char *name)
+{
+ char *p;
+
+ if (!name || !*name)
+ return NULL;
+ p = strrchr(name, '/');
+ if (!p)
+ return para_strdup(name);
+ p++;
+ if (!*p)
+ return NULL;
+ return para_strdup(p);
+}
+
+/**
+ * simple search and replace routine
+ *
+ * \param src source string
+ * \param macro_name the name of the macro
+ * \param replacement the replacement format string
+ *
+ * Replace \a macro_name(arg) by \a replacement. \a replacement is a format
+ * string which may contain a single string conversion specifier which gets
+ * replaced by 'arg'.
+ *
+ * \return A string in which all matches in \a src are replaced, or NULL if \a
+ * macro_name was not found in \a src. Caller must free the result.
+ *
+ * \sa regcomp(3)
+ */
+__must_check __malloc char *s_a_r(const char *src, const char* macro_name,
+ const char *replacement)
+{
+ regex_t preg;
+ size_t nmatch = 1;
+ regmatch_t pmatch[1];
+ int eflags = 0;
+ char *dest = NULL;
+ const char *bufptr = src;
+
+ if (!macro_name || !replacement || !src)
+ return para_strdup(src);
+ regcomp(&preg, macro_name, 0);
+ while (regexec(&preg, bufptr, nmatch, pmatch, eflags)
+ != REG_NOMATCH) {
+ char *tmp, *arg, *o_bracket, *c_bracket;
+
+ o_bracket = strchr(bufptr + pmatch[0].rm_so, '(');
+ c_bracket = o_bracket? strchr(o_bracket, ')') : NULL;
+ if (!c_bracket)
+ goto out;
+ tmp = para_strdup(bufptr);
+ tmp[pmatch[0].rm_so] = '\0';
+ dest = para_strcat(dest, tmp);
+ free(tmp);
+
+ arg = para_strdup(o_bracket + 1);
+ arg[c_bracket - o_bracket - 1] = '\0';
+ tmp = make_message(replacement, arg);
+ free(arg);
+ dest = para_strcat(dest, tmp);
+ free(tmp);
+ bufptr = c_bracket;
+ bufptr++;
+ }
+ dest = para_strcat(dest, bufptr);
+// PARA_DEBUG_LOG("%s: returning %s\n", __func__, dest);
+out:
+ regfree(&preg);
+ return dest;
+}
+
+/**
+ * replace a string according to a list of macros
+ *
+ * \param macro_list the array containing a macro/replacement pairs.
+ * \param src the source string
+ *
+ * This function just calls s_a_r() for each element of \a macro_list.
+ */
+__must_check __malloc char *s_a_r_list(struct para_macro *macro_list, char *src)
+{
+ struct para_macro *mp = macro_list;
+ char *ret = NULL, *tmp = para_strdup(src);
+
+ while (mp->name) {
+ ret = s_a_r(tmp, mp->name, mp->replacement);
+ free(tmp);
+ if (!ret)
+ return src; /* FIXME: shouldn't we continue here? */
+ tmp = ret;
+ mp++;
+ }
+ //PARA_DEBUG_LOG("%s: returning %s\n", __func__, dest);
+ return ret;
+}
+
+/**
+ * cut trailing newline
+ *
+ * \param buf the string to be chopped.
+ *
+ * Replace the last character in \a buf by zero if it is euqal to
+ * the newline character.
+ */
+void chop(char *buf)
+{
+ int n = strlen(buf);
+ if (!n)
+ return;
+ if (buf[n - 1] == '\n')
+ buf[n - 1] = '\0';
+}
+
+/**
+ * get a random filename
+ *
+ * This is by no means a secure way to create temporary files in a hostile
+ * direcory like /tmp. However, it is OK to use for temp files, fifos, sockets
+ * that are created in ~/.paraslash. Result must be freed by the caller.
+ */
+__must_check __malloc char *para_tmpname(void)
+{
+ struct timeval now;
+ gettimeofday(&now, NULL);
+ srand(now.tv_usec);
+ return make_message("%08i", rand());
+}
+
+/**
+ * create unique temporary file
+ *
+ * \param template the template to be passed to mkstemp()
+ * \param mode the desired mode of the tempfile
+ *
+ * This wrapper for mkstemp additionally uses fchmod() to
+ * set the given mode of the tempfile if mkstemp() returned success.
+ * Return value: The file descriptor of the temp file just created on success.
+ * On errors, -E_MKSTEMP or -E_FCHMOD is returned.
+ */
+__must_check int para_mkstemp(char *template, mode_t mode)
+{
+ int tmp, fd = mkstemp(template);
+
+ if (fd < 0)
+ return -E_MKSTEMP;
+ tmp = fchmod(fd, mode);
+ if (tmp >= 0)
+ return fd;
+ close(fd);
+ unlink(template);
+ return -E_FCHMOD;
+}
+
+/**
+ * get the logname of the current user
+ *
+ * \return A dynammically allocated string that must be freed by the caller. On
+ * errors, the string "unknown user" is returned, i.e. this function never
+ * returns NULL.
+ */
+__must_check __malloc char *para_logname(void)
+{
+ struct passwd *pw = getpwuid(getuid());
+ return para_strdup(pw? pw->pw_name : "unknown_user");
+}
+
+/**
+ * get the home directory of the current user
+ *
+ * \return A dynammically allocated string that must be freed by the caller. If
+ * the home directory could not be found, this function returns "/tmp".
+ */
+__must_check __malloc char *para_homedir(void)
+{
+ struct passwd *pw = getpwuid(getuid());
+ return para_strdup(pw? pw->pw_dir : "/tmp");
+}
+
+/**
+ * split string and return pointers to its parts.
+ *
+ * \param args the string to be split
+ * \param argv_ptr pointer to the list of substrings
+ * \param delim delimiter
+ *
+ * This function modifies \a args by replacing each occurance of \a delim by
+ * zero. A NULL-terminated array of pointers to char* is allocated dynamically
+ * and these pointers are initialized to point to the broken-up substrings
+ * within \a args. A pointer to this array is returned via \a argv_ptr.
+ *
+ * \return The number of substrings found in \a args.
+ */
+__must_check unsigned split_args(char *args, char ***argv_ptr, int delim)
+{
+ char *p = args;
+ char **argv;
+ ssize_t n = 0, i;
+
+ while ((p = strchr(p, delim))) {
+ p++;
+ n++;
+ }
+ *argv_ptr = para_malloc((n + 3) * sizeof(char *));
+ argv = *argv_ptr;
+ i = 0;
+ p = args;
+// printf("split_args: a:%s\n", p);
+ while (p) {
+ argv[i] = p;
+ p = strchr(p, delim);
+ if (p) {
+// printf("a:%s\n", p);
+ *p = '\0';
+// printf("b:\n");
+ p++;
+ i++;
+ }
+ }
+ argv[n + 1] = NULL;
+ return n;
+}
+
+/**
+ * ensure that file descriptors 0, 1, and 2 are valid
+ *
+ * Common approach that opens /dev/null until it gets a file descriptor greater
+ * than two.
+ *
+ * \sa okir's Black Hats Manual.
+ */
+void valid_fd_012(void)
+{
+ while (1) {
+ int fd;
+
+ fd = open("/dev/null", O_RDWR);
+ if (fd < 0)
+ exit(EXIT_FAILURE);
+ if (fd > 2) {
+ close(fd);
+ break;
+ }
+ }
+}
+
+/**
+ * get the own hostname
+ *
+ * \return A dynammically allocated string containing the hostname.
+ *
+ * \sa uname(2)
+ */
+__malloc char *para_hostname(void)
+{
+ struct utsname u;
+
+ uname(&u);
+ return para_strdup(u.nodename);
+}
--- /dev/null
+/*
+ * Copyright (C) 2006 Andre Noll <maan@systemlinux.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+ */
+
+/** \file string.h exported sybmols from string.c */
+
+/** contains name/replacement pairs used by s_a_r_list()
+ *
+ * \sa s_a_r()
+ */
+struct para_macro {
+ /** the name of the macro */
+ char *name;
+ /** the replacement text */
+ char *replacement;
+};
+__must_check __malloc void *para_realloc(void *p, size_t size);
+__must_check __malloc void *para_malloc(size_t size);
+__must_check __malloc void *para_calloc(size_t size);
+__must_check __malloc char *para_strdup(const char *s);
+__must_check __malloc __printf_1_2 char *make_message(const char *fmt, ...);
+__must_check __malloc char *para_strcat(char *a, const char *b);
+__must_check __malloc char *para_dirname(const char *name);
+__must_check __malloc char *para_basename(const char *name);
+__must_check __malloc char *s_a_r(const char *src, const char* regex, const char *replacement);
+__must_check __malloc char *s_a_r_list(struct para_macro *pm, char *src);
+void chop(char* buf);
+__must_check __malloc char *para_tmpname(void);
+__must_check int para_mkstemp(char *template, mode_t mode);
+__must_check __malloc char *para_logname(void);
+__must_check __malloc char *para_homedir(void);
+__must_check unsigned split_args(char *args, char ***argv_ptr, int delim);
+__malloc char *para_hostname(void);
+void valid_fd_012(void);
+
--- /dev/null
+#include "para.h"
+/** \file time.c helper functions for dealing with timeouts */
+
+/**
+ * convert struct timeval to milliseconds
+ *
+ * \param tv the value to convert
+ *
+ * \return The number off milliseconds in \a tv
+ */
+long unsigned tv2ms(const struct timeval *tv)
+{
+ return tv->tv_sec * 1000 + (tv->tv_usec + 500)/ 1000;
+}
+
+/**
+ * convert milliseconds to struct timeval
+ *
+ * \param n the number of milliseconds
+ * \param tv will be filled in by the function
+ */
+void ms2tv(long unsigned n, struct timeval *tv)
+{
+ tv->tv_sec = n / 1000;
+ tv->tv_usec = (n % 1000) * 1000;
+}
+
+/**
+ * convert double to struct timeval
+ *
+ * \param x the value to convert
+ * \param tv converted value is stored here
+ */
+void d2tv(double x, struct timeval *tv)
+{
+ tv->tv_sec = x;
+ tv->tv_usec = (x - (double)tv->tv_sec) * 1000.0 * 1000.0 + 0.5;
+}
+
+/**
+ * compute the difference of two time values
+ *
+ * \param b minuend
+ * \param a subtrahend
+ * \param diff difference is stored here
+ *
+ * \return If b > a, compute b - a and return 1. if b < a
+ * compute a - b and return -1. If \a diff is not \p NULL, \a diff gets
+ * filled in with the absolute value |b - a|.
+ */
+int tv_diff(const struct timeval *b, const struct timeval *a, struct timeval *diff)
+{
+ int ret = 1;
+
+ if ((b->tv_sec < a->tv_sec) ||
+ ((b->tv_sec == a->tv_sec) && (b->tv_usec < a->tv_usec))) {
+ const struct timeval *tmp = a;
+ a = b;
+ b = tmp;
+ ret = -1;
+ }
+ if (!diff)
+ return ret;
+ diff->tv_sec = b->tv_sec - a->tv_sec;
+ if (b->tv_usec < a->tv_usec) {
+ diff->tv_sec--;
+ diff->tv_usec = 1000 * 1000 - a->tv_usec + b->tv_usec;
+ } else
+ diff->tv_usec = b->tv_usec - a->tv_usec;
+ return ret;
+}
+
+/**
+ * add two structs of type timeval
+ *
+ * \param a first addend
+ * \param b second addend
+ * \param sum holds the sum upon return
+ */
+void tv_add(const struct timeval *a, const struct timeval *b,
+ struct timeval *sum)
+{
+ sum->tv_sec = a->tv_sec + b->tv_sec;
+ if (a->tv_usec + b->tv_usec >= 1000 * 1000) {
+ sum->tv_sec++;
+ sum->tv_usec = a->tv_usec + b->tv_usec - 1000 * 1000;
+ } else
+ sum->tv_usec = a->tv_usec + b->tv_usec;
+}
+
+/**
+ * compute integer multiple of given struct timeval
+ *
+ * \param mult the integer value to multiply with
+ * \param tv the timevalue to multiply
+ * \param result holds mult * tv upon return
+ */
+void tv_scale(const unsigned long mult, const struct timeval *tv,
+ struct timeval *result)
+{
+ result->tv_sec = mult * tv->tv_sec;
+ result->tv_sec += tv->tv_usec * mult / 1000 / 1000;
+ result->tv_usec = tv->tv_usec * mult % (1000 * 1000);
+}
+
+/**
+ * compute fraction of given struct timeval
+ *
+ * \param div the integer value to divide by
+ * \param tv the timevalue to divide
+ * \param result holds (1 / mult) * tv upon return
+ */
+void tv_divide(const unsigned long div, const struct timeval *tv,
+ struct timeval *result)
+{
+ long unsigned q = tv->tv_usec / div;
+ result->tv_sec = tv->tv_sec / div;
+ result->tv_usec = (tv->tv_sec - result->tv_sec * div) * 1000 * 1000 / div;
+ if (result->tv_usec + q >= 1000 * 1000) {
+ result->tv_sec++;
+ result->tv_usec = 1000 * 1000 - result->tv_usec - q;
+ } else
+ result->tv_usec += q;
+}
+
+/**
+ * compute a convex combination of two time values
+ *
+ * \param a the first coefiicent
+ * \param tv1 the first time value
+ * \param b the second coefiicent
+ * \param tv2 the second time value
+ * \param result holds the convex combination upon return
+ *
+ * compute x := (a * tv1 + b * tv2) / (|a| + |b|). a, b may be negative.
+ * returns sign(x) and stores |x| in the result pointer.
+ */
+int tv_convex_combination(const long a, const struct timeval *tv1,
+ const long b, const struct timeval *tv2,
+ struct timeval *result)
+{
+ struct timeval tmp1, tmp2, tmp3;
+ int ret = 1, subtract = ((a > 0 && b < 0) || (a < 0 && b > 0));
+ unsigned long a1 = ABS(a), b1 = ABS(b);
+
+ tv_scale(a1, tv1, &tmp1);
+ tv_scale(b1, tv2, &tmp2);
+ if (subtract)
+ ret = tv_diff(&tmp1, &tmp2, &tmp3);
+ else
+ tv_add(&tmp1, &tmp2, &tmp3);
+ if (a1 + b1)
+ tv_divide(a1 + b1, &tmp3, result);
+ else {
+ result->tv_sec = 0;
+ result->tv_usec = 0;
+ }
+ if (!a || !b) {
+ if (a + b < 0)
+ ret = -1;
+ } else
+ if (a < 0)
+ ret = -ret;
+ return ret;
+}
--- /dev/null
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.2.5 (GNU/Linux)
+
+iD8DBQBBcp6/Wto1QDEAkw8RAiLVAKCI8AyDm8nnIlUVTdMjPXvFiiigYwCgpcbP
+yRYrzN5SJeJGB9BMQwy93FA=
+=9IMv
+-----END PGP SIGNATURE-----
--- /dev/null
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.2.5 (GNU/Linux)
+
+iD8DBQBBeNU/Wto1QDEAkw8RAk+fAJ9gcyVaAWC80zkBBrIOn+xe1PYa5gCfeDkl
+mLT5IExWAQLATFGpfoXjwgY=
+=8MrY
+-----END PGP SIGNATURE-----
--- /dev/null
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.2.5 (GNU/Linux)
+
+iD8DBQBBi4NJWto1QDEAkw8RAo8LAJ9QorQ8F1/XAs3w124O2vrZYREFmgCgkm1c
+8Wk5758Y3KXL/gqhZ30FwYk=
+=rA4D
+-----END PGP SIGNATURE-----
--- /dev/null
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.2.5 (GNU/Linux)
+
+iD8DBQBBqjEFWto1QDEAkw8RAhlHAJ0Qncfz/82teh5tNXHcH2iJ31uXiwCggoeo
+A+pdWH4KH8gR3bETOR0vKY8=
+=8KSZ
+-----END PGP SIGNATURE-----
--- /dev/null
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.2.5 (GNU/Linux)
+
+iD8DBQBBuYcDWto1QDEAkw8RAv4+AJ9Cg+RVDyGwVJQrJ4/pyslvEw5ENwCdGe+p
+2k+K+awrnDwEiR8toLeKzg8=
+=5703
+-----END PGP SIGNATURE-----
--- /dev/null
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.2.5 (GNU/Linux)
+
+iD8DBQBBxaDnWto1QDEAkw8RAo4HAKCUoXYmraULQ3j1Vpskdp+mxrv8vQCdE2r2
+QkqQRJvrwih7YjOoZsgZNyo=
+=ZlPC
+-----END PGP SIGNATURE-----
--- /dev/null
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.2.5 (GNU/Linux)
+
+iD8DBQBB1YTyWto1QDEAkw8RAsrnAKCChqFSupAeJ6KxN/zu45tmLgDOawCfY2Sv
+oQvFqa+fnW4Sok8QIQTEHVc=
+=1btl
+-----END PGP SIGNATURE-----
--- /dev/null
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.2.5 (GNU/Linux)
+
+iD8DBQBCKbbOWto1QDEAkw8RAi8cAJkB0J1J917amcXhaIGrPOEwsh3N6wCffIkg
+0SbjPqZmjj0ysd07nkbfF9Y=
+=Tpn/
+-----END PGP SIGNATURE-----
--- /dev/null
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.2.5 (GNU/Linux)
+
+iD8DBQBCY+EgWto1QDEAkw8RAnKpAKCNDVXhmRbovvbtPh9B839k0I4UTACfTcM1
+37Omc/mYNF5t9RWCTDZXzMw=
+=UGev
+-----END PGP SIGNATURE-----
--- /dev/null
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.2 (GNU/Linux)
+
+iD8DBQBC9MmQWto1QDEAkw8RAicGAJ4+v+FAAuMkRp5ldeJOogt0HVz7YwCggaU1
+9X5kv752BC04Rnw9Z5gnNkk=
+=liTM
+-----END PGP SIGNATURE-----
--- /dev/null
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.2 (GNU/Linux)
+
+iD8DBQBDAOfPWto1QDEAkw8RAkogAKCq1jAc5CzjCnRzzhRKvQJah2ZSOQCfSXjO
+1emlvg4SXyru3GibEDufnzo=
+=Rt/T
+-----END PGP SIGNATURE-----
--- /dev/null
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.2.1 (GNU/Linux)
+
+iD8DBQBD9lLgWto1QDEAkw8RAg/NAJ9CbDqJWcC6T6eUxkwJXUzLO+fIuwCfUvOX
+lDCOmC7Ekgfsj9bIn8DRgqg=
+=xqpS
+-----END PGP SIGNATURE-----
--- /dev/null
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.2 (GNU/Linux)
+
+iD8DBQBDBUN5Wto1QDEAkw8RAjUXAJ9nGvPGVUGU+ohYNuwBA3ntWYAV6gCeNWTS
+JruvcovSxpZ0/fGr47P3MX4=
+=BomB
+-----END PGP SIGNATURE-----
--- /dev/null
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.2 (GNU/Linux)
+
+iD8DBQBDFygrWto1QDEAkw8RAhNwAJwMoTi7Z4UGJ8wOYQYemqKONC1D/gCfQL2e
+7UZ4vgK6SoPgfAF71KTiois=
+=Copg
+-----END PGP SIGNATURE-----
--- /dev/null
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.2 (GNU/Linux)
+
+iD8DBQBDMYSLWto1QDEAkw8RAh9RAJ48UU3Ddo6vMaAKBCKlKixDzCMJkgCgioov
+4gIO9N9Of7Xe+x2uV/qVc4Q=
+=/C+7
+-----END PGP SIGNATURE-----
--- /dev/null
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.2 (GNU/Linux)
+
+iD8DBQBDTq27Wto1QDEAkw8RAim+AKCS97CJGamp5zWyTE4TVFhcXAfsqQCfVY5F
+5u7hwO0LSFD7oken59eBaic=
+=Rc4P
+-----END PGP SIGNATURE-----
--- /dev/null
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.2 (GNU/Linux)
+
+iD8DBQBDY7w5Wto1QDEAkw8RAsigAJ9H7V6Ox+r8YCQtwyqpogvv7hn3VQCfW9oS
+kxHL+0dEFhh18oDFpFejUv4=
+=/wPn
+-----END PGP SIGNATURE-----
--- /dev/null
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.2 (GNU/Linux)
+
+iD8DBQBDsYVZWto1QDEAkw8RAs4RAJ4hmW115pTB+eqI5HljZGAUxB9V4QCeOPRT
+/nrMniVfSJ2iHHU3jtZfYrs=
+=xiVS
+-----END PGP SIGNATURE-----
--- /dev/null
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.2 (GNU/Linux)
+
+iD8DBQBDuLAWWto1QDEAkw8RAo3JAJ47Q5nCQKinSIddlAhjoU4hxpNpKgCghMd/
+TSR7233HKIudTw/4/RLgRUg=
+=gaCj
+-----END PGP SIGNATURE-----
--- /dev/null
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.2 (GNU/Linux)
+
+iD8DBQBD1aQpWto1QDEAkw8RAnxcAJ4y5Bccey0e5U1I7QdTM1reZtAtGACfUrD6
+vQtvLz1h+ULsQKfgylvHn08=
+=7FUV
+-----END PGP SIGNATURE-----
--- /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 wav.c a filter that inserts a wave header */
+
+#include "gcc-compat.h"
+#include "para.h"
+
+#include "list.h"
+#include "filter.h"
+#include "string.h"
+
+#define WAV_OUTBUF_SIZE 81920
+#define WAV_HEADER_LEN 44
+#define BITS 16
+
+/* derived from oggdec.c of the vorbis-tools-1.0.1 package */
+#define WRITE_U32(buf, x) (buf)[0] = (unsigned char)((x) & 0xff);\
+ (buf)[1] = (unsigned char)(((x) >> 8) & 0xff);\
+ (buf)[2] = (unsigned char)(((x) >> 16) & 0xff);\
+ (buf)[3] = (unsigned char)(((x) >> 24) & 0xff);
+#define WRITE_U16(buf, x) (buf)[0] = (unsigned char)((x) & 0xff);
+
+static void make_wav_header(int channels, int samplerate, struct filter_node *fn)
+{
+
+ char *headbuf = fn->buf;
+ unsigned int size = 0x7fffffff;
+ int bytespersec = channels * samplerate * BITS / 8;
+ int align = channels * BITS / 8;
+
+ PARA_DEBUG_LOG("writing wave header: %d channels, %d KHz\n", channels, samplerate);
+ memset(headbuf, 0, WAV_HEADER_LEN);
+ memcpy(headbuf, "RIFF", 4);
+ WRITE_U32(headbuf + 4, size - 8);
+ memcpy(headbuf + 8, "WAVE", 4);
+ memcpy(headbuf + 12, "fmt ", 4);
+ WRITE_U32(headbuf + 16, 16);
+ WRITE_U16(headbuf + 20, 1); /* format */
+ WRITE_U16(headbuf + 22, channels);
+ WRITE_U32(headbuf + 24, samplerate);
+ WRITE_U32(headbuf + 28, bytespersec);
+ WRITE_U16(headbuf + 32, align);
+ WRITE_U16(headbuf + 34, BITS);
+ memcpy(headbuf + 36, "data", 4);
+ WRITE_U32(headbuf + 40, size - 44);
+}
+
+static ssize_t wav_convert(char *inbuf, size_t len, struct filter_node *fn)
+{
+ size_t copy;
+ int *bof = fn->private_data;
+
+ if (*bof) {
+ make_wav_header(fn->fci->channels, fn->fci->samplerate, fn);
+ fn->loaded = WAV_HEADER_LEN;
+ *bof = 0;
+// return 0;
+ }
+ copy = MIN(len, fn->bufsize - fn->loaded);
+ memmove(fn->buf + fn->loaded, inbuf, copy);
+ fn->loaded += copy;
+// PARA_DEBUG_LOG("len = %d, copy = %d\n", len, copy);
+ return copy;
+}
+
+static void wav_close(struct filter_node *fn)
+{
+ free(fn->buf);
+ fn->buf = NULL;
+ free(fn->private_data);
+ fn->private_data = NULL;
+}
+
+static void wav_open(struct filter_node *fn)
+{
+ int *bof;
+
+ fn->bufsize = WAV_OUTBUF_SIZE;
+ fn->buf = para_malloc(fn->bufsize);
+ fn->private_data = para_malloc(sizeof(int));
+ bof = fn->private_data;
+ *bof = 1;
+ PARA_DEBUG_LOG("wav filter node: %p, output buffer: %p, loaded: %d\n",
+ fn, fn->buf, fn->loaded);
+}
+
+void wav_init(struct filter *f)
+{
+ f->convert = wav_convert;
+ f->close = wav_close;
+ f->open = wav_open;
+}
--- /dev/null
+<h3>Contact</h3>
+
+<p> André Noll, <a
+href="mailto:maan@systemlinux.org">maan@systemlinux.org</a> </p>
+
+Comments and bug reports are welcome (english, german, or spanish
+language). Please provide enough info such as the version of paraslash
+you are using and relevant parts of the logs. Including the string
+[paraslash] in the subject line is also a good idea.
--- /dev/null
+ <h3><a name="live_demo">Live Demo</a></h3>
+
+ <p> There is a public paraslash stream at www.paraslash.org,
+ streaming
+
+ <a href="http://www.digitalvibes.de/">the music of Henri Petterson</a>.
+
+
+ You can listen to the stream with any mp3 player that supports
+ http streaming. Both </p>
+
+ <p>mpg123 -Z http://www.paraslash.org:8009/</p>
+
+ and
+
+ <p>xmms http://www.paraslash.org:8009/</p>
+
+ <p> are known to work.</p>
+
+ <p> Moreover, there is an anonymous paraslash account
+ available which you can use to have a look at paraslash
+ without configuring and running para_server on your own box.
+ Just download and run
+
+ <a href="demo-script">this shell script</a>
+
+ on your Unix system. No root-privileges are required.</p>
+
--- /dev/null
+<h3><a name="documentation">Documentation</a></h3>
+<p>
+Have a look at this
+ <a href="overview.pdf">overview</a>,
+a pdf file containing a sketch which illustrates how the pieces of paraslash work
+together. Read
+ <a href="README.html">README</a>
+for general information (including a list of required software),
+ <a href="INSTALL.html">INSTALL</a>
+for installation notes, and
+ <a href="README.mysql.html">README.mysql</a>
+for instructions on how to use the mysql database tool
+shipped with paraslash.</p>
+<p>
+The various commands of para_server and para_audiod are explained in
+paraslash's
+ <a href="doc/html/index.html">manual pages</a>.
+</p>
+<p>
+As of version 0.2.10, the source of para_server and para_audiod is fully documented.
+Have a look at the
+ <a href="doxygen/html/index.html">Paraslash API Reference</a>.
+</p>
+<p> Finally, you can <a href="HTML/index.html">RTFS online</a>.
+</p>
--- /dev/null
+<h3><a name="download">Download</a></h3>
+
+<p> Only <a href="versions/">source</a> is available,
+including the <a href="versions/paraslash-cvs.tar.bz2">
+nightly cvs snapshot</a> (which may or may not compile at
+any given time). All regular releases are
+<a href="PUBLIC_KEY">cryptographically signed</a>.
+Anonymous (read-only) cvs access is also
+available. Checkout a copy with </p>
+
+<p> cvs -d
+:pserver:anonymous@cvs.systemlinux.org:/var/lib/cvs
+login </p>
+<p>(empty passwd)</p>
+<p> cvs -d :pserver:anonymous@cvs.systemlinux.org:/var/lib/cvs co paraslash </p>
--- /dev/null
+<hr>
+<!--
+ Last modified:
+ <!--#flastmod virtual="" -->
+ <p>
+ <a href="http://validator.w3.org/check?uri=referer"><img border="0"
+ src="http://www.w3.org/Icons/valid-html401"
+ alt="Valid HTML 4.01!" height="31" width="88"></a>
+ </p>
+-->
+ </td>
+ </table>
+</body>
+</html>
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=iso-8859-1'>
+ <title>Paraslash</title>
+ <LINK href="para.css" REL="stylesheet" TYPE="text/css">
+ <link rel="shortcut icon" href="paraslash.ico">
+</head>
+<body>
+ <basefont face="lucida, helvetica, arial" size="3">
+ <table border="0" cellpadding="10" cellspacing="0">
+ <tr>
+ <td>
+ <a href="/"><IMG SRC="paraslash.png" alt="paraslash" border="0"></a><BR>
+ </td>
+ <td>
+ <h3>Paraslash: Play, archive, rate and stream
+ large audio sets happily</h3>
+
+ A set of tools for doing just what its name
+ suggests.
+ </td>
+ </tr>
+ <tr>
+ <td valign="TOP">
+ <br><a href="index.html">Home</a>
+ <br><a href="NEWS.html">News</a>
+ <br><a href="FEATURES.html">Features</a>
+ <br><a href="download.html">Download</a>
+ <br><a href="screenshots.html">Screenshots</a>
+ <br><a href="demo.html">Live Demo</a>
+ <br><a href="ChangeLog.html">Changelog</a>
+ <br><a href="documentation.html">Documentation</a>
+ <br><a href="license.html">License</a>
+ <br><a href="contact.html">Contact</a>
+ <br><a href="CREDITS.html">Credits</a>
+ </td>
+ <td Valign="TOP">
+ <hr>
--- /dev/null
+<h3>Events</h3>
+<ul>
+ <li>2006-02-17: <a href="versions/paraslash-0.2.10.tar.bz2">paraslash-0.2.10</a>
+ <a href="versions/paraslash-0.2.10.tar.bz2.asc">(sig)</a> "cyclic attractor"</li>
+ <li>2006-02-16: <a href="doxygen/html/index.html">API Reference</a> online</li>
+ <li>2006-01-24: paraslash-0.2.9 "progressive turbulence"</li>
+ <li>2006-01-02: paraslash-0.2.8 "dynamic accumulation"</li>
+ <li>2005-12-27: paraslash-0.2.7 "transparent invariance"</li>
+ <li>2005-11-12: new web pages</li>
+ <li>2005-10-29: paraslash-0.2.6 "recursive compensation"</li>
+ <li>2005-10-27: <a href="doc/html/index.html">manual pages</a> online</li>
+ <li>2005-10-13: paraslash-0.2.5 "aggressive resolution"</li>
+ <li>2005-09-21: paraslash-0.2.4 "toxic anticipation"</li>
+ <li>2005-09-01: paraslash-0.2.3 "hydrophilic movement"</li>
+ <li>2005-08-19: paraslash-0.2.2 "tangential excitation"</li>
+ <li>2005-08-15: paraslash-0.2.1 "surreal experience"</li>
+ <li>2005-08-06: overview.pdf</li>
+ <li>2005-08-06: paraslash-0.2.0 "distributed diffusion"</li>
+ <li>2005-08-01: paraslash live stream</li>
+ <li>2005-04-18: paraslash-0.1.7 "melting penetration"</li>
+ <li>2005-03-05: paraslash-0.1.6 "asymptotic balance"</li>
+ <li>2004-12-31: paraslash-0.1.5 "opaque eternity"</li>
+ <li>2004-12-19: paraslash-0.1.4 "tunneling transition"</li>
+ <li>2004-12-10: paraslash-0.1.3 "vanishing inertia"</li>
+ <li>2004-11-28: paraslash-0.1.2 "spherical fluctuation"</li>
+ <li>2004-11-05: paraslash-0.1.1 "floating atmosphere"</li>
+ <li>2004-10-22: paraslash-0.1.0 "rotating cortex"</li>
+</ul>
--- /dev/null
+<h3><a name="license">License</a></h3>
+
+Paraslash is licensed under the <a
+href="http://www.gnu.org/licenses/gpl.html">GNU General
+Public License</a>, which is generally just abbreviated
+as the GPL license, or just the GPL. Check out the
+<a href="http://www.gnu.org/cgi-bin/license-quiz.cgi">GPL
+License Quiz</a> or take a look at the <a
+href="http://www.gnu.org/licenses/gpl-faq.html">Frequently Asked
+Questions about the GNU GPL</a>. For the differences between
+the established version 2 and the upcoming version 3 of the GPL, see this
+<a href="http://lia.ch/~sb/GPL_docdiff-2-3draft1.html"> colored
+diff</a>.
--- /dev/null
+BODY,H1,H2,H3,H4,H5,H6,P,CENTER,TD,TH,UL,DL,DIV {
+ font-family: Geneva, Arial, Helvetica, sans-serif;
+}
+BODY,TD {
+ font-size: 90%;
+}
+H1 {
+ text-align: center;
+ font-size: 160%;
+}
+H2 {
+ font-size: 120%;
+}
+H3 {
+ font-size: 100%;
+}
+CAPTION { font-weight: bold }
+DIV.qindex {
+ width: 100%;
+ background-color: #482e02;
+ border: 1px solid #84b007;
+ text-align: center;
+ margin: 2px;
+ padding: 2px;
+ line-height: 140%;
+}
+DIV.nav {
+ width: 100%;
+ background-color: #48ee02;
+ border: 1px solid #84b007;
+ text-align: center;
+ margin: 2px;
+ padding: 2px;
+ line-height: 140%;
+}
+DIV.navtab {
+ background-color: #48ee02;
+ border: 1px solid #84b007;
+ text-align: center;
+ margin: 2px;
+ margin-right: 15px;
+ padding: 2px;
+}
+TD.navtab {
+ font-size: 70%;
+}
+A.qindex {
+ text-decoration: none;
+ font-weight: bold;
+ color: #1A419D;
+}
+A.qindex:visited {
+ text-decoration: none;
+ font-weight: bold;
+ color: #1A419D
+}
+A.qindex:hover {
+ text-decoration: none;
+ background-color: #ffff00;
+}
+A.qindexHL {
+ text-decoration: none;
+ font-weight: bold;
+ background-color: #cccc00;
+ color: #000000;
+ border: 1px double #929502;
+}
+A.qindexHL:hover {
+ text-decoration: none;
+ background-color: #ffff00;
+ color: #000000;
+}
+A.qindexHL:visited { text-decoration: none; background-color: #e6c60c; color: #000000 }
+A.el { text-decoration: none; font-weight: bold }
+A.elRef { font-weight: bold }
+A.code:link { text-decoration: none; font-weight: normal; color: ##BA3708}
+A.code:visited { text-decoration: none; font-weight: normal; color: ##BA3708}
+A.codeRef:link { font-weight: normal; color: #BA3708}
+A.codeRef:visited { font-weight: normal; color: #BA3708}
+A:hover { text-decoration: none; background-color: #ffff00 }
+DL.el { margin-left: -1cm }
+.fragment {
+ font-family: Fixed, monospace;
+ font-size: 95%;
+}
+PRE.fragment {
+ border: 1px solid #CCCCCC;
+ background-color: #351505;
+ margin-top: 4px;
+ margin-bottom: 4px;
+ margin-left: 2px;
+ margin-right: 8px;
+ padding-left: 6px;
+ padding-right: 6px;
+ padding-top: 4px;
+ padding-bottom: 4px;
+}
+DIV.ah { background-color: black; font-weight: bold; color: #000000; margin-bottom: 3px; margin-top: 3px }
+TD.md { background-color: #112244; font-weight: bold; }
+TD.mdPrefix {
+ background-color: #112244;
+ color: #606060;
+ font-size: 80%;
+}
+TD.mdname1 { background-color: #112244; font-weight: bold; color: #00CC00; }
+TD.mdname { background-color: #112244; font-weight: bold; color: #00CC00; width: 600px; }
+DIV.groupHeader {
+ margin-left: 16px;
+ margin-top: 12px;
+ margin-bottom: 6px;
+ font-weight: bold;
+}
+DIV.groupText { margin-left: 16px; font-style: italic; font-size: 90% }
+BODY {
+ background: black;
+ color: #bbbbbb;
+ margin-right: 20px;
+ margin-left: 20px;
+}
+TD.indexkey {
+ background-color: #383e12;
+ font-weight: bold;
+ padding-right : 10px;
+ padding-top : 2px;
+ padding-left : 10px;
+ padding-bottom : 2px;
+ margin-left : 0px;
+ margin-right : 0px;
+ margin-top : 2px;
+ margin-bottom : 2px;
+ border: 1px solid #CCCCCC;
+}
+TD.indexvalue {
+ background-color: #383e12;
+ font-style: italic;
+ padding-right : 10px;
+ padding-top : 2px;
+ padding-left : 10px;
+ padding-bottom : 2px;
+ margin-left : 0px;
+ margin-right : 0px;
+ margin-top : 2px;
+ margin-bottom : 2px;
+ border: 1px solid #CCCCCC;
+}
+TR.memlist {
+ background-color: #112244;
+}
+P.formulaDsp { text-align: center; }
+IMG.formulaDsp { }
+IMG.formulaInl { vertical-align: middle; }
+SPAN.keyword { color: #008000 }
+SPAN.keywordtype { color: #00CCCC }
+SPAN.keywordflow { color: #108000 }
+SPAN.comment { color: #00CCCC }
+SPAN.preprocessor { color: #CC00CC }
+SPAN.stringliteral { color: #e0e020 }
+SPAN.charliteral { color: #0000ff }
+.mdTable {
+ border: 1px solid #868686;
+ background-color: #112244;
+}
+.mdRow {
+ padding: 8px 10px;
+}
+.mdescLeft {
+ padding: 0px 8px 4px 8px;
+ font-size: 80%;
+ font-style: italic;
+ background-color: #000000;
+ border-top: 1px none #10E010;
+ border-right: 1px none #202010;
+ border-bottom: 1px none #202010;
+ border-left: 1px none #202010;
+ margin: 0px;
+}
+.mdescRight {
+ padding: 0px 8px 4px 8px;
+ font-size: 80%;
+ font-style: italic;
+ background-color: #000000;
+ border-top: 1px none #20E210;
+ border-right: 1px none #202010;
+ border-bottom: 1px none #202010;
+ border-left: 1px none #202010;
+ margin: 0px;
+}
+.memItemLeft {
+ padding: 1px 0px 0px 8px;
+ margin: 4px;
+ border-top-width: 1px;
+ border-right-width: 1px;
+ border-bottom-width: 1px;
+ border-left-width: 1px;
+ border-top-color: #FFFF10;
+ border-right-color: #FFFF10;
+ border-bottom-color: #FFFF10;
+ border-left-color: #FFFF10;
+ border-top-style: solid;
+ border-right-style: none;
+ border-bottom-style: none;
+ border-left-style: none;
+ background-color: #000000;
+ font-size: 80%;
+}
+.memItemRight {
+ padding: 1px 8px 0px 8px;
+ margin: 4px;
+ border-top-width: 1px;
+ border-right-width: 1px;
+ border-bottom-width: 1px;
+ border-left-width: 1px;
+ border-top-color: #E0E000;
+ border-right-color: #E0E000;
+ border-bottom-color: #E0E000;
+ border-left-color: #E0E000;
+ border-top-style: solid;
+ border-right-style: none;
+ border-bottom-style: none;
+ border-left-style: none;
+ background-color: #000000;
+ font-size: 80%;
+}
+.memTemplItemLeft {
+ padding: 1px 0px 0px 8px;
+ margin: 4px;
+ border-top-width: 1px;
+ border-right-width: 1px;
+ border-bottom-width: 1px;
+ border-left-width: 1px;
+ border-top-color: #E0E000;
+ border-right-color: #E0E000;
+ border-bottom-color: #E0E000;
+ border-left-color: #E0E000;
+ border-top-style: none;
+ border-right-style: none;
+ border-bottom-style: none;
+ border-left-style: none;
+ background-color: #000000;
+ font-size: 80%;
+}
+.memTemplItemRight {
+ padding: 1px 8px 0px 8px;
+ margin: 4px;
+ border-top-width: 1px;
+ border-right-width: 1px;
+ border-bottom-width: 1px;
+ border-left-width: 1px;
+ border-top-color: #E0E000;
+ border-right-color: #E0E000;
+ border-bottom-color: #E0E000;
+ border-left-color: #E0E000;
+ border-top-style: none;
+ border-right-style: none;
+ border-bottom-style: none;
+ border-left-style: none;
+ background-color: #000000;
+ font-size: 80%;
+}
+.memTemplParams {
+ padding: 1px 0px 0px 8px;
+ margin: 4px;
+ border-top-width: 1px;
+ border-right-width: 1px;
+ border-bottom-width: 1px;
+ border-left-width: 1px;
+ border-top-color: #E0E000;
+ border-right-color: #E0E000;
+ border-bottom-color: #E0E000;
+ border-left-color: #E0E000;
+ border-top-style: solid;
+ border-right-style: none;
+ border-bottom-style: none;
+ border-left-style: none;
+ color: #606060;
+ background-color: #3A3A1A;
+ font-size: 80%;
+}
+.search { color: #f03309;
+ font-weight: bold;
+}
+FORM.search {
+ margin-bottom: 0px;
+ margin-top: 0px;
+}
+INPUT.search { font-size: 75%;
+ color: #800000;
+ font-weight: normal;
+ background-color: #000000;
+}
+TD.tiny { font-size: 75%;
+}
+a {
+ color: #BA4108;
+}
+a:visited {
+ color: #BA3708;
+}
+.dirtab { padding: 4px;
+ border-collapse: collapse;
+ border: 1px solid #84b007;
+}
+TH.dirtab { background: #080e02;
+ font-weight: bold;
+}
+HR { height: 1px;
+ border: none;
+ border-top: 1px solid yellow;
+}
+
--- /dev/null
+<h3><a name="license">Screenshots</a></h3>
+
+<p> The main part of paraslash is a client-server application, so
+it doesn't have much to take a screenshot of. However, here is the
+startup part of
+
+ <a href="para_server-startup.txt">para_server's logfile</a>
+
+and a similar thing for the logfile of
+
+ <a href="para_audiod-startup.txt">para_audiod</a>.</p>
+
+Moreover, there are screenshots of
+
+ <a href="gui-2005-11-12.png">para_gui</a>,
+
+ <a href="sdl_gui.jpg">para_sdl_gui</a>,
+
+ <a href="para_krell-2005-02.png">para_krell</a>
+
+and
+ <a href="para_slider-2004-12.png">para_slider</a>.
+