From: Andre Date: Mon, 20 Feb 2006 06:03:36 +0000 (+0100) Subject: initial git commit X-Git-Tag: v0.2.11~71 X-Git-Url: http://git.tue.mpg.de/?a=commitdiff_plain;h=2ed89c59f0efcd0a2763f47c7d3455663241e623;p=paraslash.git initial git commit Let's try if this works out. --- 2ed89c59f0efcd0a2763f47c7d3455663241e623 diff --git a/.changelog_before_cvs b/.changelog_before_cvs new file mode 100644 index 00000000..7f952550 --- /dev/null +++ b/.changelog_before_cvs @@ -0,0 +1,378 @@ +This is the old Changelog, before the icc project switched to cvs. + +dbtool (sa): fix SEGFAULT when some attributes are NULL +icc_dbtool.template: add help command +Makefile: Check for mp3info +dbtool.c: Handle files with «'» properly + +****** Version 91 (Sat Nov 30 13:52:37 MET 2002) ******* +Makefile: include icc_dbtool.conf.sample +icebear: Remove unneeded signal handlers +icc_dbtool.c Add documentation to upd command +Documentation update +dbtool.c: Add config file functionality +icc_dbtool.c: Add upd command +command.c: Bug: cs prints current stream in err msg +zombies, zombies +many fixes all over the place +dbtool.c: new command: vrfy (verify entries in database) +dbtool.c: implement clean command +icc_dbtool: implement skip +dont use pointers to malloced mem in shared area (causes SEGFAULT) +icebear: SEGFAULT when cs was called after server had got HUP. Nasty bug +icc_bash_completion: adapt to new syntax +command.c: nuke drop_ss and new_ss +dbtool.c: Add da (drop attribut) command (replaces old drop_ss) +dbtool.c: Add na (new attribut) command (replaces old new_ss) +Makefile: Changelog contains entries not yet done + +****** Version 90 (Wed Nov 13 02:05:34 MET 2002) ******* +icebear: kill unneeded signal handlers +icebear: call info only if it is a valid command +server: code cleanup +command.c uptime: print #connections and #commands +server: count #connections and #commands +server/icebear/command: count/display number of songs already played +icc.h: cleanup +command.c: nuke ms command +dbtool.c: add summary command +dbtool.c: new command: sa, replaces old ss (set subset) command +dbtool.c: new command: streams +server: renew command list on SIGHUP +icebear: call us only if it is a valid command +command.c: sort command list alphabetically +server: Nicify logging +command.c: New command: perms +command.c: Rewrite command handlers to use linked list +command.c: introdule linked list of commands + +****** Version 89 (Sat Nov 9 17:27:32 MET 2002) ******* +command.c: Major cleanup + +****** Version 88 (Sat Nov 9 01:34:14 MET 2002) ******* +icebear: New command: cs +init_icebear put default_stream to freshly allocated shared mem +server: pass default_stream to init_icebear +server: new configfile option: default_stream +command.c help: call icc_dbtool help + +****** Version 87 (Fri Nov 8 23:03:03 MET 2002) ******* +Makefile: new target icc_dbtool +icc_dbtool: Switch to mysql C API + +****** Version 86 (Thu Oct 17 22:38:37 MEST 2002) ******* +New file: README.dbtool +client/server: use OPENSSL define of config.h instead of hard coded path +client/server: include config.h +README: add icecast to requirements + +****** Version 85 (Thu Oct 10 22:56:29 MEST 2002) ******* +Makefile: Don't include tdldb in tgz +Makefile: New target: distclean (clean no longer removes Changelog, TODO and version.h) +dbtool ms: print each dot only once (reduces size of ps from 171kb to 22kb) + +****** Version 84 (Fri Oct 4 15:01:54 MEST 2002) ******* +icc_server.conf.sample/icecast.conf.sample: Use same passwd +Makefile: include icecast.conf.sample in tarball +Makefile: mkdir ~/public_html + +****** Version 83 (Wed Sep 25 01:59:06 MEST 2002) ******* +Makefile tgz: php crap got messed up + +****** Version 82 (Wed Sep 25 01:56:55 MEST 2002) ******* +server: Reread configuration on SIGHUP +Makefile: New Changelog format +Makefile tgz: create archive with leading directory icc-$version + +************* Version 81 ************* +server update_str: fix bug: uptime string messed up +README: add description of icc_bash_completion +INSTALL: Update, extentions and nicifying +icc_bash_completion: adapt to new layout of «icc help« + +************* Version 80 ************* +server: print uptime in welcome message +server: new functions: uptime,uptime_str +command.c: new commands: uptime,version +icebear: exec_cmd: test if stopped for ff/jmp/next. Fixes Segfault when eg «next» was called with no file opened yet + +************* Version 79 ************* +server: implement remaining part of configuration part +server: implement basic configuration file functionality + +************* Version 78 ************* +Makefile: install icc_client.conf.sample and also include it in tgz +new file: icc_client.conf.sample +client: actually print version and info if loglevel is at least info +client: new function: print_version +client: read_config: change verbose setting to loglevel +client: new option -l (loglevel) obsoletes -v (verbose) +client: Fix «if(verbose==TRUE)» madness, use loglevels instead +client: write log function similar to server's log function +README: Update icc_client description: Delete sentence on telnet + +************* Version 77 ************* +client: take localhost as default host, not p133 +client: new option -c (specify config file) +client: implement reading of config file + +************* Version 76 ************* +server sigchld_handler: Add exit status also as numerical value +command.c: Add description for drop_ss, fut, us + +************* Version 75 ************* +icebear: fix segfault on nomore (double close mp3_stream) +icebear: nm->nomore +command.c: add nomore command (got forgotten) +client: catch SIGCHLD +client: introduce internal variable debug to toggle debug before command line argumants are parsed (for verbose switch) +client: Nicify verbose output +client: Fix command line parsing + +************* Version 74 ************* +Makefile tgz: use prefix icc-0.0. +Makefile tgz: bug: forget to read VERSION +Makefile: Add .tdldb to sources + +************* Version 73 ************* +client: new option -V (version) +server: new option -V (version) +Makefile: new target: tgz (implies version) +Makefile: fix version numbering +Makefile: include INSTALL in sources +Makefile: Use -p Option for tar + +************* Version 72 ************* +icc_server: print info on who we are serving to argv[0] +icebear: print status to argv[0] +command.c: Add missing descriptions to all internal commands +dbtool.template: Use find instead of locate +server: send proper error message if client sent unkown command + +************* Version 71 ************* +client: Added help option -h +command.c help: Nicify output. Include needed permissions. +command.c: Add description for stat, sb and term +Makefile: Add target dbtool_install +Makefile install: don't install dbtool +server/client: print version number +icc.h include version.h +Makefile: added target version.h +added INSTALL +README: Remove Warning message concerning lack of authentification +Makefile: implement PREFIX +icebear: typo in jmp command +icc_dbtool.template us: Don't print warning +Makefile install: Install also icc_dbtool.template + +************* Version 70 ************* + +************* Version 69 ************* +icc_dbtool.template: Change fut so that it does not need bogosort anymore + +************* Version 68 ************* +Makefile: include icc_dbtool.template in tgz package +server: new command line options: P, i, ip +server: kill global variable conn +command.c parse_cmd: Fix SEGV when in interactive mode and no option given (reported by Christof Müller) +Makefile: all: dont make TODO and Changelog +icc.client: remove passwd +icc.h remove maan + +************* Version 67 ************* +icebear: delete option for id3 comment in mp3info call +icebear: Fix bug: pollret returns strange events +icebear: fix division by 0 bug when length of file is zero seconds + +************* Version 66 ************* + +************* Version 65 ************* +command.c: perror -> log +command.c: new fuction: check_permissions +command.c: insert dummy at the beginning of command list to let start command numbers by 1 instead of 0 + +************* Version 64 ************* +server: add string strerror(errno) to log file entries +command.c: make parse_command only return command number and call handle_cmd from main +server: print log msg _before_ exit(1) if send fails +server: exit(1) if send fails + +************* Version 63 ************* +command.c: code cleanup +icc_server: implement option -c (specify config file) +client: fix SEGFAULT when command line ends with invalid option +client: add option -k (secret key file) + +************* Version 62 ************* +implement permission check for all commands + +************* Version 61 ************* +implement basic configuration file functionality +icc_server/client: implement rsa authentification via openssl + +************* Version 60 ************* +command.c: add help text for jmp and ff commands +command.c: help: print command handler and needed privileges +icebear: minor code cleanups +icebear: Nicify log output +icebear: split poll_cmd_listener to allow blocking reads => no more sleep(1) + +************* Version 59 ************* +icebear: Code documentation +icebear: move shout init/shoutdown to exec_cmd. This might fix pause command. + +************* Version 58 ************* +icebear: increase number of allowed invalid files before giving up to 100 +dbtool clean: Handle files with «'» properly + +************* Version 57 ************* +dbtool: add summary command +icc_process_form.php: change strcmp(a,b)=0 to not strcmp(a,b) + +************* Version 56 ************* + +************* Version 55 ************* +kill id3 code +dbtool: change info and fut to avoid Lost connection to MySQL server during query +icebear: It dies if there no valid songs. Stop and lurk for play instead +icebear: code cleanup + +************* Version 54 ************* +icebear/icc.h: use long unsigned int to avoid overflow in status bar + +************* Version 53 ************* +server: BUG in help: dont check args if args=NULL +command.c help: implement »help command« +README: add LICENSE +README: include description to php scripts +server: kill guics command +server: kill log command +dbtool: kill pw +dbtool: add local for all local vars + +************* Version 52 ************* +command.c, icc.h: cleanup declaration of command struct +dbtool us: add option to specify update time +add bash completion for icc_client + +************* Version 51 ************* +kill spl command +dbtool: add ls command +icc_server: add ls command +icc_server: kill cd command + +************* Version 50 ************* +php/icc_form, php/icc_process_form: fix several bugs + +************* Version 49 ************* +add background gradient image + +************* Version 48 ************* +write mp3 search form icc_form.php +icebear: log seconds with 2 digits +icc_gui: Add sleep(2) before each reconnect +php: delete unneeded pics + +************* Version 47 ************* +icc_server: add time and date to log + +************* Version 46 ************* +php/icc_dbgui: fix off by one bug +dbtool: clean reports files as deleted which are still there +remove wtc.jpg + +*** Version 45 *** +improve php scripts + +*** Version 44 *** +add php scripts for apache/mysql + +*** Version 43 *** +icc.h changed #define ERR to #define ERROR + +*** Version 42 *** +gui: make it survive window resize + +*** Version 41 *** +Add short description of all icc tools to README + +*** Version 40 *** +dbtool fut: replace use of ancient anplay variable by aprropriate one +dbtool ss: make it work again when args _are_ given +server: Add -p option +client: catch SIGINT + +*** Version 39 *** +dbtool: ps does not work if no arg is given +dbtool: ss does not work if no arg is given +server: log GPL banner (loglevel INFO) +client: Add GPL banner in welcome message +dbtool last: print full filenames +dbtool info: print name if no argument was given + +*** Version 38 *** +icebear: make jmp,ff,next,pause,play,stop,nm actually respond via new pipe +create new pipe icebear->server for responses to icebear commands + +*** Version 37 *** +dbtool cs: print info if no argument is given + +*** Version 36 *** +icebear: kill getenv code +icebear: rewrite startup code to prevent icebear from playing on startup + +*** Version 35 *** +icebear: nicify log messages +icebear: make pause, play, stop work in all possible cases + +*** Version 34 *** +Makefile version: include COPYRIGHT, GPL and README files in tarball +Add COPYRIGHT and GPL files +*.c *.h dbtool: Add GPL headers +inplement option -L logfile +client: Fix bug: commands with options don't work + +*** Version 33 *** +Makefile: only put completed todos into Changelog +server: implement Daemon mode +icc_client: Dont send empty lines + +*** Version 32 *** +command: Fix nasty bug which caused sending 2 nullbytes instead of only one +dbtool info: print info on current song if no argument was given + +*** Version 31 *** +dbtool: Another silly bug in info which prevented dir to be printed +client: Fix silly bug caused it to send wrong number of bytes +icc_server: implemented term command +Makefile: strip away useless tdl info +icc_dbtool: Changed Warning message for commands not yet implemented +icc_server: Added help command +icc_client: add readline support +icc_dbtool fut: Fix bug when there is exactly one new song +db_tool: implement skip +db_tool: implement ss +db_tool: implement clean +db_tool: implement upd +dbtool: implemented pw +move icc ms to dbtool +icc_shell: ms +icc_server/dbtool: new command hist +dbtool: implement last +created icc_shell +make icebear use icc_dbtool instead of mp3 +write db_tool basics (fut, ass, ps, info, us) +Makefile improvements +icc_client: Add verbose flag -v +Adjust loglevels and make logging nicer +icc_client: properly handle command line options +icc_server: Dont start playing on startup +take output of info command as misc info +icc_client: treat nr_options correctly +log messages cleanup +log proper exit status (WIFSIGNALED...) +icebear: stop actually restarts icebear +icc_client: interactive mode +icc_server: on errors send messages clients + + diff --git a/1.0 b/1.0 new file mode 100644 index 00000000..f1c2f4f0 --- /dev/null +++ b/1.0 @@ -0,0 +1,2 @@ +- lyrics +- gui: bot window scrollable diff --git a/COPYING b/COPYING new file mode 100644 index 00000000..0847591d --- /dev/null +++ b/COPYING @@ -0,0 +1,16 @@ +Copyright (C) 1997-2005 Andre Noll + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + diff --git a/CREDITS b/CREDITS new file mode 100644 index 00000000..981a99c9 --- /dev/null +++ b/CREDITS @@ -0,0 +1,41 @@ +Credits +======= + +Several people helped by reporting bugs, improving documentation, +constructive discussions, or, last but not least, by writing free +software on which this project is based on. + +These people include: + +Karl Bartel (SFont) + +Lorenzo Bettini (gengetopt) + +Ricardo Cerqueira (mp3info) + +Thierry Excoffier (libzmw) + +Thomas Forell (bug reports) + +Silke Klassert (bug reports) + +Jaroslav Kysela (aplay) + +Robert Leslie (libmad) + +Simon Morlat (ortp) + +Christof Müller (bug reports) + +M. Hari Nezumi (AudioCompress) + +Manuel Odendahl (poc) + +Christian Reißmann (design) + +Michael Smith (vcut) + +Cedric Tefft (mp3info) + +Linus Torvalds (for giving us one hell of an +operating system [quote taken from README.linux for DOOM v1.666]) diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 00000000..090e1206 --- /dev/null +++ b/Doxyfile @@ -0,0 +1,1232 @@ +# Doxyfile 1.4.6 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = paraslash + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = web/sync/doxygen + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, +# Dutch, Finnish, French, German, Greek, Hungarian, Italian, Japanese, +# Japanese-en (Japanese with English messages), Korean, Korean-en, Norwegian, +# Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, +# Swedish, and Ukrainian. + +OUTPUT_LANGUAGE = English + +# This tag can be used to specify the encoding used in the generated output. +# The encoding is not always determined by the language that is chosen, +# but also whether or not the output is meant for Windows or non-Windows users. +# In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES +# forces the Windows encoding (this is the default for the Windows binary), +# whereas setting the tag to NO uses a Unix-style encoding (the default for +# all platforms other than Windows). + +USE_WINDOWS_ENCODING = NO + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like the Qt-style comments (thus requiring an +# explicit @brief command for a brief description. + +JAVADOC_AUTOBRIEF = YES + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the DETAILS_AT_TOP tag is set to YES then Doxygen +# will output the detailed description near the top, like JavaDoc. +# If set to NO, the detailed description appears after the member +# documentation. + +DETAILS_AT_TOP = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for Java. +# For instance, namespaces will be presented as packages, qualified scopes +# will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want to +# include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = NO + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from the +# version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and 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 , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES (the default) +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES (the default) +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = YES + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = web/header2.html + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = web/footer.html + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be +# generated containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = NO + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = NO + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = HAVE_MAD HAVE_ORTP HAVE_OGGVORBIS HAVE_MYSQL __GNUC__=4 __GNUC_MINOR__=4 + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = YES + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will +# generate a call dependency graph for every global function or class method. +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_WIDTH = 1024 + +# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_HEIGHT = 1024 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that a graph may be further truncated if the graph's +# image dimensions are not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH +# and MAX_DOT_GRAPH_HEIGHT). If 0 is used for the depth value (the default), +# the graph is not depth-constrained. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, which results in a white background. +# Warning: Depending on the platform used, enabling this option may lead to +# badly anti-aliased labels on the edges of a graph (i.e. they become hard to +# read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = YES diff --git a/FEATURES b/FEATURES new file mode 100644 index 00000000..3dde8e97 --- /dev/null +++ b/FEATURES @@ -0,0 +1,71 @@ +Features +======== + +configurable audio streaming software +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + para_server streams binary audio data (mp3/ogg files) over + local or remote networks. It contains two built-in streamers: + the http streamer and the ortp streamer. + + para_audiod runs on the client side and connects to + para_server. The audio stream is read back and sent through + any of paraslash's filters (mp3 decoder, ogg vorbis decoder, + volume normalizer,...) and the resulting stream is written to + an external program's standard in, usually an audio player, + like para_play that uses alsa. It is possible to grab the + stream at any position in the filter chain. + + The receiving/filtering software is also available as + standalone command line tool: para_recv grabs the http or ortp + stream and writes to stdout; para_filter reads from stdin, + converts the stream according to the given --filter command + line options and writes the transformed stream to stdout. + +mysql-based audio file selector: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + the (optional) mysql database tool manages some statistics on + your audio files. It contacts the mysql server to decide which + song to play next by sending a user-defined sql-query. This + allows rather sophisticated configurations and is explained + in detail in README.mysql. + +small memory footprint: +~~~~~~~~~~~~~~~~~~~~~~~ + paraslash is lightweight. The stripped binary of para_server + with all its features compiled in (mysql/dopey dbtool, + mp3/ogg support, http/ortp support) is about 100K on i386 + under Linux. para_audiod is even smaller. + +command line interface, including shell: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + para_client without arguments starts interactive (shell) + mode. Otherwise, command is sent to para_server directly + and output is dumped to stdout. This can be used by any + scripting language to produce user interfaces with very little + programming effort. + +authentication/encryption via openssl: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + All connections between para_server and para_client are + encrypted by default. For each user of paraslash you must + create a public/secret key pair for authentication/encryption. + +various user interfaces and utilities: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + o para_gui. Curses based interface, displays information in a + curses window and can be used to easily control para_server + and para_audiod. + + o para_sdl_gui. Shows pictures (on a per song basis) and + other information about the current audio file. Can be used + as a screen saver. + + o para_krell. A gkrellm2 plugin that shows small pics and is + able to launch 27 different commands by clicking on the image. + + o para_slider. User-friendly stream creator for people who + don't like their keyboard. + + o para_dbadm. Simple curses interface for changing attributes. + + o para_fade. Simple volume fader and alarm clock. diff --git a/GPL b/GPL new file mode 100644 index 00000000..60549be5 --- /dev/null +++ b/GPL @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + 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.) + +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. + + 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. + + 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 + + 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. + + + Copyright (C) 19yy + + 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. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/INSTALL b/INSTALL new file mode 100644 index 00000000..3b373181 --- /dev/null +++ b/INSTALL @@ -0,0 +1,212 @@ +Paraslash install notes +======================= + +Any knowledge of how to work with mouse and icons is not required. + +Install all needed packages +~~~~~~~~~~~~~~~~~~~~~~~~~~~ +See README for a list of required software. Don't be afraid of the long +list of unusal libraries: Most of them are only needed for optional +programs. Autoconf will detect what is installed on your system and +will only build those executables that can be built with your setup. + + +Install server and client +~~~~~~~~~~~~~~~~~~~~~~~~~ +Install the package on all machines, you'd like this software to run on: + + (./configure && make) > /dev/null + +There should be no errors (but probably many warnings about missing +software). Then, as root, + + make install + + +Setup user list and create rsa keys +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +If you already have your rsa keys, skip this step. If you are new +to paraslash, you have to generate an rsa key pair for each user you +want to allow to connect. You need at least one user. + +Let's assume that you'd like to run the server on host server_host +as user foo, and that you want to connect from client_host as user bar. + +As foo@server_host, create ~/.paraslash/server.users: + + target=~/.paraslash/server.users + key=~/.paraslash/key.pub.bar + perms=DB_READ,DB_WRITE,AFS_READ,AFS_WRITE + mkdir -p ~/.paraslash + echo "user bar $key $perms" >> $target + +This gives bar full privileges. + +Change to the bar account on client_host and generate the key-pair +with the commands + + key=~/.paraslash/key.bar + mkdir -p ~/.paraslash + (umask 077 && openssl genrsa -out $key) + +Next, extract its public part: + + pubkey=~/.paraslash/key.pub.bar + openssl rsa -in $key -pubout -out $pubkey + +and copy the public key just created to server_host: + + scp $pubkey foo@server_host:.paraslash/ + +Finally, tell para_client to connect to server_host: + + echo 'hostname server_host' > ~/.paraslash/client.conf + +Start para_server +~~~~~~~~~~~~~~~~~ + para_server + +Now you can use para_client to connect to the server and issue +commands. Open a new shell (as "bar" on "client_host" in the above +example) and try + + para_client help + para_client si + +to retrieve the list of available commands and some server info. + + +Choose your database tool (dbtool) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +You have three options: + + 1. Use the mysql dbtool which comes with paraslash and requires + mysql. This is recommended. + + 2. Use your own database tool. If you have that already, + skip this step. + + 3. If you can not use the mysql dbtool and you just want + to quickly make paraslash working, use the dopey dbtool. + The directory which is searched for audio files can be given + via the server option --dopey_dir. + + Note, however, that dopey is _really_ dopey. It scans + $dopey_dir on every audio file change and chooses one + randomly. You get the idea. Have a look at its source code + and feel free to modify. + + +The current database tool can be changed at runtime via + + para_client cdt new_dbtool + +If you have choosen 1. above, read README.mysql and follow the +instructions given there. Return to this document when ready. + + +Start streaming manually +~~~~~~~~~~~~~~~~~~~~~~~~ + + para_client play + para_client stat 2 + +This starts streaming and dumps some information on the current song +to stdout. + +You should now be able to listen to the stream with any player +capable of reading from stdin. To check this, try the following +on client_host: + + mp3: + + para_recv -r http:-i:server_host | para_filter -f mp3 -f wav | para_play + or + mpg123 http://server_host:8000/ + or + xmms http://server_host:8000/ + + ogg: + + para_recv -r http:-i:server_host | para_filter -f ogg -f wav | para_play + +If this works, proceede. Otherwise doublecheck what is logged by +para_server and use the --loglevel option of para_recv to increase +verbosity. + +Configure para_audiod +~~~~~~~~~~~~~~~~~~~~~ +In order to automatically start the right decoder at the right time +and to offer to the clients some information on the current audio +stream and on paraslash's internal state, you should run the local +audio daemon, para_audiod, on every machine that is supposed to play +the audio stream. Try + + para_audiod -h + +for help. Usually you have to specify at least server_host as the +receiver specifier, like this: + + -r http:-i:server_host + +The prefered way to use para_audiod is to run it once at system start +as an unprivileged user. para_audiod needs to create a "well-known" +socket for the clients to connect to. If you want to change the +default socket (e.g. because you do not have write access for the +directory where the socket resides), use the -s option or the config +file to change the default. Note that in this case you'll also have +to specify the same value for para_audioc's -s option. + +If para_server is playing, you should be able to listen to the audio +stream as soon as para_audiod is started. Once it is running, try + + para_audioc stat + +That should dump some information to stdout. Other commands include + + para_audioc off + para_audioc on + para_audioc sb + para_audioc term + para_audioc cycle + + +Start para_gui +~~~~~~~~~~~~~~ +para_gui reads the output of "para_audioc stat" and displays that +information in a curses window. It also allows you to bind keys to +arbitrary commands. There are several flavours of key-bindings: + + o internal: These are the built-in commands that can not be + changed (help, quit, loglevel, version...). + + o external: Shutdown curses before launching the given command. + Useful for starting other ncurses programs from within + para_gui, e.g. aumix or para_dbadm. Or, use + + para_client mbox + + to write a mailbox containing one mail for each file + in the mysql database and start mutt from within para_gui + to browse your collection! + + o display: Launch the command and display its stdout in + para_gui's bottom window. + + o para: Like display, but start "para_client " instead of "". + + +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 +(english, german, or spanish language). Please provide enough info +such as the version of paraslash you are using and relevant parts of +the logs. diff --git a/Makefile.in b/Makefile.in new file mode 100644 index 00000000..25cec46f --- /dev/null +++ b/Makefile.in @@ -0,0 +1,277 @@ +COPYRIGHT = Copyright (c) 1997-2006 by Andre Noll +DISCLAIMER = This is free software with ABSOLUTELY NO WARRANTY. See COPYING for details. + +prefix = @prefix@ +exec_prefix = @exec_prefix@ + +BINDIR = @bindir@ +VARDIR = /var/paraslash +PKGDATADIR = @datadir@/@PACKAGE_NAME@ +CONFDIR = $(PKGDATADIR)/samples +FONTDIR = $(PKGDATADIR)/fonts +PICDIR = $(PKGDATADIR)/pics +MANDIR = @prefix@/share/man/man1 + +install_sh = @install_sh@ +SSL_LIBS = @SSL_LIBS@ +SSL_LDFLAGS = @SSL_LDFLAGS@ + +extra_binaries = @extra_binaries@ +build_date = $(shell date) +system = $(shell uname -rs) +cc_version = $(shell $(CC) --version | head -n 1) +version = @PACKAGE_VERSION@ +codename = atomic duality + +DEBUG_CPPFLAGS += -Wno-sign-compare -g -Wunused -Wundef -W + +# produces lots of warnings on older debian systems with gcc-2.95.4 +# DEBUG_CPPFLAGS += -Wredundant-decls + +# produces false positives +# DEBUG_CPPFLAGS += -Wunreachable-code + +# invalid options for gcc-2.95.4 +# CPPFLAGS += -Wfloat-equal +# CPPFLAGS += -Wmissing-format-attribute +# CPPFLAGS += -Wunused-macros + +# invalid option for gcc-3.3.5 +# CPPFLAGS += -Wextra + +CPPFLAGS += -Os +CPPFLAGS += -Wall +CPPFLAGS += -Wuninitialized +CPPFLAGS += -Wstrict-prototypes +CPPFLAGS += -Wchar-subscripts +CPPFLAGS += -Wformat-security +CPPFLAGS += -DBINDIR='"$(BINDIR)"' +CPPFLAGS += -DFONTDIR='"$(PKGDATADIR)/fonts"' +CPPFLAGS += -DPICDIR='"$(PKGDATADIR)/pics"' +CPPFLAGS += -DBUILD_DATE='"$(build_date)"' +CPPFLAGS += -DSYSTEM='"$(system)"' +CPPFLAGS += -DVERSION='"$(version)"' +CPPFLAGS += -DCODENAME='"$(codename)"' +CPPFLAGS += -DCC_VERSION='"$(cc_version)"' +CPPFLAGS += -Werror-implicit-function-declaration + +BINARIES = para_server para_client para_gui para_audiod para_audioc para_recv para_filter $(extra_binaries) + +FONTS := $(wildcard fonts/*.png) +PICS := $(wildcard pics/paraslash/*.jpg) +MANS := $(wildcard doc/man/man1/*.1) +sample_conf := $(wildcard *.conf.sample) +gengetopts := $(wildcard *.ggo) +gengetopts_c := $(gengetopts:.ggo=.cmdline.c) +gengetopts_h := $(gengetopts:.ggo=.cmdline.h) +all_c_files := $(wildcard *.c) +c_sources := $(filter-out $(gengetopts_c), $(all_c_files)) +grutatxt := COPYING ChangeLog NEWS README.mysql CREDITS INSTALL README \ + FEATURES GPL +grutatxt_html := $(grutatxt:=.html) +html_in := $(wildcard web/*.in.html) +gen_html := $(subst web/,web/sync/,$(html_in)) +gen_html := $(gen_html:.in.html=.html) +gruta_in := $(grutatxt:=.in.html) +gruta_in := $(patsubst %,web/%,$(gruta_in)) +gruta_html := $(grutatxt:=.html) +gruta_html := $(patsubst %,web/sync/%,$(gruta_html)) +shots := gui-2005-11-12.png para_audiod-startup.txt +shots += para_krell-2005-02.png para_server-startup.txt +shots += para_slider-2004-12.png sdl_gui.jpg para_krell-2005-02.png +shots := $(patsubst %,web/sync/%,$(shots)) +web_pics := web/sync/paraslash.png web/sync/paraslash.ico +web_misc := demo-script overview.pdf versions/paraslash-cvs.tar.bz2 PUBLIC_KEY key.anonymous para.css doc +web_misc := $(patsubst %,web/sync/%,$(web_misc)) + +misc := bash_completion +headers := para.h server.h SFont.h crypt.h list.h http.h send.h ortp.h rc4.h \ + close_on_fork.h afs.h db.h gcc-compat.h recv.h filter.h audiod.h \ + grab_client.h error.h net.h ringbuffer.h daemon.h string.h +scripts := install-sh configure +autocrap := Makefile.in config.h.in configure.ac autogen.sh +tarball := web/sync/doc pics fonts $(c_sources) $(sample_conf) $(headers) \ + $(misc) $(grutatxt) $(gengetopts) $(autocrap) $(gengetopts_c) $(gengetopts_h) \ + $(scripts) + +.PHONY: clean distclean maintainer-clean install html www tags ChangeLog doxygen +all: $(BINARIES) +www: $(gen_html) $(gruta_html) $(web_pics) $(web_misc) $(shots) tags doxygen + +client_objs = client.cmdline.o client.o net.o string.o crypt.o +gui_objs = gui.cmdline.o gui.o gui_common.o exec.o close_on_fork.o signal.o string.o gui_theme.o stat.o ringbuffer.o +sdl_gui_objs = sdl_gui.cmdline.o SFont.o sdl_gui.o gui_common.o exec.o close_on_fork.o string.o stat.o +dbadm_objs = dbadm.o exec.o close_on_fork.o string.o +fade_objs = fade.cmdline.o fade.o exec.o close_on_fork.o string.o +krell_objs = krell.o string.o +slider_objs = slider.o string.o +audioc_objs = audioc.cmdline.o audioc.o string.o net.o +play_objs = play.cmdline.o play.o time.o + +*.o: para.h config.h gcc-compat.h + +include Makefile.deps + +V := ($(TGZ_PREFIX)@PACKAGE_STRING@, $(codename))\n$(COPYRIGHT)\n$(DISCLAIMER) +module_ggo_opts := --set-version="" --set-package="" + +grab_client.cmdline.h grab_client.cmdline.c: grab_client.ggo + gengetopt $(module_ggo_opts) \ + --no-handle-error \ + --no-handle-help \ + --arg-struct-name=grab_client_args_info \ + --file-name=$(subst .ggo,,$<).cmdline \ + --func-name $(subst .ggo,,$<)_cmdline_parser < $< + grep -v 'fprintf\ *(stderr' $(subst .ggo,,$<).cmdline.c > $(subst .ggo,,$<).tmp + mv $(subst .ggo,,$<).tmp $(subst .ggo,,$<).cmdline.c + ggo_help GRAB_HELP_TXT < grab_client.ggo >> $(subst .ggo,,$<).cmdline.h + +%_recv.cmdline.h %_recv.cmdline.c: %_recv.ggo + gengetopt $(module_ggo_opts) \ + --file-name=$(subst .ggo,,$<).cmdline \ + --func-name $(subst .ggo,,$<)_cmdline_parser < $< + +%_filter.cmdline.h %_filter.cmdline.c: %_filter.ggo + gengetopt $(module_ggo_opts) \ + --file-name=$(subst .ggo,,$<).cmdline \ + --func-name $(subst _filter.ggo,,$<)_cmdline_parser < $< + +%.cmdline.h %.cmdline.c: %.ggo + case $< in client.ggo) O="--unamed-opts=command";; \ + audioc.ggo) O="--unamed-opts=command";; \ + esac; \ + gengetopt $$O --conf-parser --file-name=$(*F).cmdline --set-package="para_$(subst .cmdline,,$(*F))" --set-version="$V" < $< + +ortp_send.o: ortp_send.c + $(CC) -c -Wall -o $@ -g @GLIB_CFLAGS@ $< +ortp_recv.o: ortp_recv.c + $(CC) -c -Wall -o $@ -g @GLIB_CFLAGS@ $< + +para_recv: @recv_objs@ + $(CC) @recv_objs@ -o $@ @recv_ldflags@ + +slider.o: slider.c + $(CC) -c -Wall -o $@ -g @GLIB_CFLAGS@ @GTK_CFLAGS@ $< + +krell.o: krell.c + $(CC) -Wall -O -g -fPIC @GTK_CFLAGS@ -c -o $@ krell.c + +%.cmdline.o: %.cmdline.c + $(CC) -c $(CPPFLAGS) $< +%.o: %.c + $(CC) -c $(CPPFLAGS) $(DEBUG_CPPFLAGS) $< + +para_filter: @filter_objs@ + $(CC) @filter_objs@ -o $@ @filter_ldflags@ + +para_slider: $(slider_objs) + $(CC) $(slider_objs) -o $@ @GTK_LIBS@ @GLIB_LIBS@ -lzmw + +para_client: $(client_objs) + $(CC) -o $@ $(client_objs) $(SSL_LDFLAGS) -lreadline -lncurses $(SSL_LIBS) + +para_gui: $(gui_objs) + $(CC) -o $@ $(gui_objs) -lncurses + +para_audiod: @audiod_objs@ + $(CC) -o $@ @audiod_objs@ @audiod_ldflags@ + +para_audioc: $(audioc_objs) + $(CC) -o $@ $(audioc_objs) + +para_dbadm: $(dbadm_objs) + $(CC) -o $@ $(dbadm_objs) -lncurses -lmenu + +para_fade: $(fade_objs) + $(CC) -o $@ $(fade_objs) + +para_server: @server_objs@ + $(CC) -o $@ @server_objs@ @server_ldflags@ + +para_sdl_gui: $(sdl_gui_objs) + $(CC) -o $@ $(sdl_gui_objs) -lSDL_image + +para_play: $(play_objs) + $(CC) -o $@ $(play_objs) -lasound + +para_compress: $(compress_objs) + $(CC) -o $@ $(compress_objs) + +para_krell.so: $(krell_objs) + $(CC) -Wall -fPIC @GTK_CFLAGS@ krell.o -o $@ @GTK_LIBS@ -shared + +clean: + rm -f *.o $(BINARIES) + +distclean: clean + rm -f Makefile autoscan.log config.status config.log && \ + rm -rf web/sync/* autom4te.cache aclocal.m4 + rm -f GPATH GRTAGS GSYMS GTAGS + +maintainer-clean: distclean + rm -f $(gengetopts_c) $(gengetopts_h) *.tar.bz2 \ + $(grutatxt_html) ChangeLog* config.h configure \ + config.h.in + +install: all + umask 022 && \ + mkdir -p $(BINDIR) $(VARDIR) $(VARDIR)/fifo && \ + $(install_sh) -s -m 755 $(BINARIES) $(BINDIR) && \ + mkdir -p $(CONFDIR) && \ + $(install_sh) -m 644 bash_completion $(sample_conf) $(CONFDIR) + mkdir -p $(FONTDIR) && \ + $(install_sh) -m 644 $(FONTS) $(FONTDIR) && \ + mkdir -p $(PICDIR) && \ + $(install_sh) -m 644 $(PICS) $(PICDIR) && \ + mkdir -p $(MANDIR) && \ + $(install_sh) -m 644 $(MANS) $(MANDIR) + + +@PACKAGE_TARNAME@-@PACKAGE_VERSION@.tar.bz2: all $(gengetopts_c) $(tarball) + dir=@PACKAGE_TARNAME@-@PACKAGE_VERSION@ &&\ + mkdir -p $${dir} && \ + cp -a $(tarball) $${dir} && \ + find $${dir} -name "CVS" | xargs rm -rf && \ + tar cpjf $@ $${dir} && \ + rm -rf $${dir} && \ + ls -l $@ + +ChangeLog: + para_util changelog > $@ +web/%.in.html: % + grutatxt -nb < $< > $@ +tags: + rm -rf web/sync/HTML && gtags && htags -nF && mv HTML web/sync +web/sync/doc: + para_util doc +web/header2.html: web/header.html + sed -e 's|href="|href="\.\.\/\.\./|g' \ + -e 's|SRC="|SRC="\.\.\/\.\./|g' $< > $@ +doxygen: web/header2.html + mkdir -p web/sync/doxygen + doxygen +web/sync/doxygen: + mkdir -p $@ +web/sync/doxygen/index.html: +web/sync/%.html: web/%.in.html web/header.html web/footer.html web/sync + cat web/header.html $< web/footer.html > $@ +web/sync/%.png: pics/web/%.png web/sync + cp $< $@ +web/sync/%.ico: pics/web/%.ico web/sync + cp $< $@ +web/sync/demo-script: scripts/demo-script web/sync + cp $< $@ +web/sync/para.css: web/para.css web/sync + cp $< $@ +web/sync/versions/paraslash-cvs.tar.bz2: paraslash-cvs.tar.bz2 web/sync + cp -a versions web/sync && cp $< $@ +web/sync/overview.pdf: skencil/overview.pdf web/sync + cp $< $@ +web/sync/%: % + cp $< $@ +web/sync/%: pics/screenshots/% + cp $< $@ +skencil/%.ps: skencil/%.sk + sk2ps $< > $@ +%.pdf: %.ps + ps2pdf - - < $< > $@ diff --git a/NEWS b/NEWS new file mode 100644 index 00000000..81f317d1 --- /dev/null +++ b/NEWS @@ -0,0 +1,954 @@ +NEWS +==== + +0.?.? (to be announced) "atomic duality" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + o update to gengetopt-2.16 + +0.2.10 (2006-02-17) "cyclic attractor" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Huge documentation update, a scrollable window for para_gui, ortp +improvements, and of course many small fixes not mentioned here. +The diffstat below is rather misleading as most insertions are due +to the new source documentation. + + o autoconf cleanup + + o para_server also uses the new error subsystem + + o lots of new documentation (UTSL) + + o gui improvements: + - keysyms for cursor keys and for next/previous page keys + - scrollable output window + - new internal commands: scroll up/down, page up/down + - fix color of command output. + + o ortp: the --chunk_time and --header flags are no longer needed + for para_recv/para_audiod as this information is now encoded in + each rtp packet sent by para_server. + +New files: daemon.h net.h ringbuffer.c ringbuffer.h string.h web/para.css +Doxyfile + + Makefile.in | 99 ++----- + NEWS | 19 + + README | 6 + README.mysql | 21 - + afs.c | 223 +++++++++------ + afs.h | 140 ++++++++-- + audioc.c | 5 + audiod.c | 503 +++++++++++++++++------------------- + audiod.h | 11 + client.c | 12 + close_on_fork.c | 43 +++ + close_on_fork.h | 1 + command.c | 283 ++++++++------------ + compress.c | 34 +- + configure.ac | 402 +++++++++++++--------------- + crypt.c | 63 ++-- + crypt.h | 12 + daemon.c | 76 ++++- + db.c | 90 ++++-- + db.h | 97 ++++++ + dbadm.c | 23 + + dopey.c | 59 ++-- + error.h | 315 +++++++++++++++++----- + exec.c | 55 +++ + fade.c | 4 + filter.c | 23 - + filter.ggo | 2 + filter.h | 307 +++++++++++++++++++-- + filter_chain.c | 147 +++++++--- + grab_client.c | 87 ++++-- + grab_client.h | 41 +- + gui.c | 422 ++++++++++++++++++++++-------- + gui_common.c | 2 + http.h | 1 + http_recv.c | 99 ++++--- + http_send.c | 164 ++++++++--- + list.h | 5 + mp3.c | 246 +++++++++-------- + mp3dec.c | 88 +++--- + mysql.c | 339 ++++++++++-------------- + net.c | 271 ++++++++++++++----- + ogg.c | 86 +++--- + oggdec.c | 86 +++--- + ortp.h | 31 ++ + ortp_recv.c | 162 ++++++----- + ortp_recv.ggo | 2 + ortp_send.c | 138 +++++---- + para.h | 93 ------ + play.c | 20 - + recv.c | 11 + recv.h | 147 +++++++++- + recv_common.c | 9 + sdl_gui.c | 21 - + send.h | 82 +++++ + server.c | 119 +++++--- + server.h | 117 ++++++-- + signal.c | 70 ++++- + slider.c | 5 + stat.c | 89 +++++- + string.c | 243 ++++++++++------- + time.c | 78 ++++- + wav.c | 26 - + web/documentation.in.html | 14 - + web/header.html | 13 + web/index.in.html | 1 + 65 files changed, 4146 insertions(+), 2357 deletions(-) + +0.2.9 (2006-01-24) "progressive turbulence" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Internal audiod receivers/filters, the new error subsystem and +a lot of small improvements. + + o para_recv and para_filter are integrated into the para_audiod + binary, i.e. audiod no longer spawns a new process for + each receiver/filter. As para_recv and para_filter might be + useful as standalone programs, they still get built (linked + against the same object files that are also used for audiod). + + o further ortp timing improvements should reduce the CPU usage + of the ortp receiver. + + o improved audio grabbing. The 'grab' command of para_audiod + has its own set of command line options. Read the output of + "para_audioc help grab" for more info. + + o oggdec: configurable input prebuffer size. + + o the new error subsystem gives better error diagnostics + and reduces code size. + +New files: audiod.h error.h grab_client.c grab_client.ggo grab_client.h +http_recv.ggo ortp_recv.ggo recv_common.c + + FEATURES | 49 - + INSTALL | 22 + Makefile.in | 87 +- + NEWS | 73 ++ + README | 89 +- + README.mysql | 18 + audioc.c | 5 + audiod.c | 1243 +++++++++++++++----------------------- + audiod.ggo | 97 ++ + command.c | 5 + compress.c | 44 - + compress_filter.ggo | 4 + configure.ac | 127 +++ + daemon.c | 12 + exec.c | 34 - + filter.c | 66 +- + filter.h | 24 + filter_chain.c | 126 ++- + gcc-compat.h | 8 + http_recv.c | 56 + + http_send.c | 35 - + mp3.c | 12 + mp3dec.c | 27 + net.c | 133 ++-- + oggdec.c | 108 +-- + oggdec_filter.ggo | 3 + ortp.h | 2 + ortp_recv.c | 290 +++++--- + ortp_send.c | 187 ++--- + para.h | 17 + play.c | 17 + recv.c | 101 +-- + recv.ggo | 15 + recv.h | 51 + + scripts/demo-script | 12 + server.c | 15 + signal.c | 11 + skencil/overview.sk | 300 ++++----- + stat.c | 11 + string.c | 170 ++++- + wav.c | 12 + web/demo.in.html | 72 -- + web/download.in.html | 3 + web/index.in.html | 1 + web/license.in.html | 5 + web/screenshots.in.html | 4 + 46 files changed, 2042 insertions(+), 1761 deletions(-) + +0.2.8 (2006-01-02) "dynamic accumulation" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The new modular filter design and the para_play-hangs bugfix. + + o new executable: para_filter. It combines para_mp3dec, + para_oggdec and para_compress. It also adds a further filter + type, wav, that just inserts a wave header at the desired point + of the filter chain. All 'piping' is done in-memory (i.e. no + read/write operations are used). + + o para_play: fix a stupid bug that caused it to hang under + certain circumstances. + +New files: compress_filter.ggo filter.c filter.ggo file filter.h +filter_chain.c oggdec_filter.ggo wav.c + + INSTALL | 4 + Makefile.in | 36 +++++--- + NEWS | 15 +++ + README | 25 ++---- + command.c | 8 + + compress.c | 149 ++++++++++++++++++++---------------- + configure.ac | 37 +++++--- + http_recv.c | 121 ++++++++++++++++++----------- + mp3dec.c | 216 +++++++++++++++++++++------------------------------- + oggdec.c | 223 ++++++++++++++++++++++++++++++++++++------------------ + ortp_recv.c | 167 ++++++++++++++++++++-------------------- + ortp_send.c | 2 + play.c | 2 + recv.c | 44 +++++++--- + recv.ggo | 12 -- + recv.h | 37 +++++++- + server.ggo | 2 + web/index.in.html | 1 + 18 files changed, 631 insertions(+), 470 deletions(-) + +0.2.7 (2006-12-27) "transparent invariance" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Not many user-visible changes but a fair amount of internal improvements. + + + o The http sender buffers data if it can not be sent + out immediately (because the socket is not writable). This + should prevent para_server from shutting down the connection + too early on a loaded network. + + o para_play also prebuffers data if it is told to start at a + future time by the --start_time option. + + o The return of para_recv: It combines para_ortp_recv and + para_http_recv. Use the --receiver option to switch between + the two. para_recv builds without libortp, but contains + only the http receiver in this case. + + o update to ortp 0.8.1. As this ortp release contains incompatible + changes, para_recv-0.2.7 won't link against older ortp libs. + + o improved ortp timings. + + o use of gcc-extensions that #define away for non-gcc and + gcc < 3.0. + +New files: gcc-compat.h + + CREDITS | 14 +- + FEATURES | 10 - + INSTALL | 36 +++-- + Makefile.in | 171 +++++++++++++++++---------- + NEWS | 30 ++++ + README | 128 ++++++++------------ + README.mysql | 4 + afs.c | 113 +++++++++++++----- + afs.h | 2 + audioc.c | 21 +++ + audiod.c | 226 +++++++++++++++++++++--------------- + audiod.ggo | 4 + client.c | 9 - + command.c | 71 +++++++---- + compress.c | 15 +- + compress.ggo | 4 + configure.ac | 30 ++-- + crypt.c | 2 + daemon.c | 6 + db.c | 4 + dbadm.c | 46 ++----- + dopey.c | 16 +- + fade.c | 3 + gui.c | 77 ++++++------ + http_recv.c | 143 +++++++++++++---------- + http_send.c | 217 ++++++++++++++++++++++------------- + index.html | 154 +++++++++++++------------ + list.h | 361 ----------------------------------------------------------- + mp3.c | 17 +- + mp3dec.c | 5 + mysql.c | 57 ++++++--- + net.c | 12 + + ogg.c | 26 ++-- + oggdec.c | 34 +++-- + ortp.h | 2 + ortp_recv.c | 263 +++++++++++++++++++++--------------------- + ortp_send.c | 96 ++++++++++----- + para.h | 51 +++----- + play.c | 173 +++++++++++++++++++--------- + play.ggo | 2 + sdl_gui.c | 27 +++- + send.h | 2 + server.c | 100 ++++++++-------- + stat.c | 68 ++++++++--- + string.c | 41 +++--- + 45 files changed, 1500 insertions(+), 1393 deletions(-) + +0.2.6 (2005-10-29) "recursive compensation" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Transparent session encryption (uses openssl's Alleged RC4 cipher), +the internal find command and several other improvements and cleanups. + + o Encrypt the session if encryption is requested by the client + (default for para_client 0.2.6). This is backwards + compatible, so older clients can still connect to para_server + 0.2.6. Use the new client option --plain to request an + uncrypted session (off by default, must be set to on in + order to connect to para_server 0.2.x with 0 <= x <= 5). + + o para_server uses an internal function to locate audio files + rather than calling find(1). The server option + --mysql_audio_file_dir replaces --mysql_find_cmd. + + o documentation update + + o man pages + + o header file cleanup + + o para_client code cleanup + + o para_gui: faster display of output of display commands + +New files: afs.h close_on_fork.c close_on_fork.h db.c db.h rc4.h + + 1.0 | 3 + INSTALL | 51 +- + Makefile.in | 53 +-- + NEWS | 28 + + README | 27 - + README.mysql | 45 +- + SFont.c | 20 - + afs.c | 51 -- + audioc.c | 6 + audiod.c | 303 +++++++++-------- + client.c | 174 +++------- + client.ggo | 1 + command.c | 608 ++++++++++++++++++++--------------- + compress.c | 3 + compress.ggo | 4 + configure.ac | 2 + crypt.c | 50 +- + crypt.h | 2 + dopey.c | 136 +++++-- + exec.c | 39 -- + fade.c | 4 + gui.c | 74 ++-- + http_recv.c | 4 + http_send.c | 51 +- + index.html | 12 + krell.c | 2 + mp3.c | 5 + mysql.c | 1008 ++++++++++++++++++++++++++++++++--------------------------- + net.c | 122 ++----- + ogg.c | 5 + ortp_send.c | 37 +- + para.h | 39 +- + send.h | 2 + server.c | 49 +- + server.ggo | 2 + server.h | 68 --- + stat.c | 132 ++++--- + string.c | 33 - + 38 files changed, 1738 insertions(+), 1517 deletions(-) + +0.2.5 (2005-10-13) "aggressive_resolution" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +This release adds internal senders, i.e. no more external programs are +spawned for sending out the audio data. There are two different senders +available: The http sender and the ortp sender (former para_send which +is no longer needed). + +The new sender code has a plugin-like design so it can be easily +extended should there be be any future need for supporting another +network streaming protocol. All senders are completely independent of +each other. In particular, the http and the ortp sender can operate +in parallel. + + o new server command: sender to control senders at runtime. + Read the output of "para_server -h" and "para_client help + sender" for more information. + + o para_recv renamed to para_ortp_recv + + o new executable: para_http_recv, a simple command line + http receiver. + + o major afs/mp3/ogg code simplifications due to internal + senders. + + o ogg timing improvements + + o fix several minor memory leaks (found by valgrind) + + o empty stream definitions work again + + o com_ne(): ignore errors on remove + + o audiod: fix segfault on server restart + +New files: http.h http_recv.c http_recv.ggo http_send.c ortp.h ortp_recv.c + ortp_recv.ggo ortp_send.c + + FEATURES | 27 +++- + INSTALL | 19 +-- + Makefile.in | 54 ++++----- + NEWS | 36 ++++++ + README | 45 ++++--- + afs.c | 311 +++++++++++++++------------------------------------- + audioc.c | 10 + + audiod.c | 82 +++++++------ + audiod.ggo | 2 + command.c | 184 +++++++++++++++++++++++++++--- + configure.ac | 36 ++++-- + daemon.c | 10 + + exec.c | 33 ----- + gui.c | 6 - + index.html | 6 - + mp3.c | 144 ++++-------------------- + mysql.c | 52 ++++---- + net.c | 58 ++++++++- + ogg.c | 289 ++++++++++++++---------------------------------- + oggdec.c | 19 ++- + para.h | 23 ++- + server.c | 128 ++++++++++++++------- + server.ggo | 17 ++ + server.h | 40 ++++-- + skencil/overview.sk | 86 +++----------- + string.c | 51 +++++--- + 26 files changed, 870 insertions(+), 898 deletions(-) + + +0.2.4 (2005-09-21) "toxic anticipation" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Several small improvements, fixes and the new grab command. + + o audiod: + - new command: "grab" to grab the output of the stream reader + or any filters. Read the output of "para_audioc help grab" + for more information. + - fix memory leak + - code cleanup + + o audioc: new command line option: --bufsize to specify a + buffer size different from the default size 8192. + + o improved error diagnostics for para_play. + + o new configure option: --enable-ssldir so search for openssl in + non-standard places + + o sdl_gui: Make it look nice again for 1024x768 + + o server: report total size of memory allocated with sbrk by malloc, + new command line option: --announce_time + +New files: list.h + + Makefile.in | 39 ++- + NEWS | 24 ++ + README | 22 +- + afs.c | 9 + audioc.c | 55 ++++- + audioc.ggo | 1 + audiod.c | 577 ++++++++++++++++++++++++++++++++++++++++------------------- + client.c | 20 +- + client.ggo | 12 - + command.c | 14 - + configure.ac | 64 +++--- + crypt.c | 8 + daemon.c | 6 + gui.c | 52 ++--- + gui_common.c | 70 ------- + index.html | 2 + mp3.c | 2 + mp3dec.c | 6 + net.c | 2 + ogg.c | 2 + oggdec.c | 8 + para.h | 8 + play.c | 51 ++--- + sdl_gui.c | 53 ++--- + server.c | 48 +--- + server.ggo | 1 + server.h | 2 + stat.c | 65 ++++++ + 28 files changed, 744 insertions(+), 479 deletions(-) + +0.2.3 (2005-09-01) "hydrophilic movement" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Two new executables and major feature enhancements. + + o audiod filters: It is now possible to specify arbitrary many + (including none) filters for each supported audio + format. This can be used e.g. for normalizing volume, + transforming or grabbing the audio stream, or for using + visualizers. Read the output of "para_audiod -h" for the + syntax of the new --filter_cmd option. + + o new executable: para_play, a tiny alsa player. It + can play wave files or raw pcm (16 bit little endian) + from stdin. + + o new executable: para_compress, a dynamic range compressor + intended to keep audio output at a consistent volume. Derived + from AudioCompress, http://trikuare.cx/code/AudioCompress.html. + + o audiod: New option: --stream_delay. This can be used in + a local network to syncronize the audio output of all + clients that play the same stream. + +New files: compress.c compress.ggo play.c play.ggo + + CREDITS | 2 + FEATURES | 31 +-- + Makefile.in | 31 ++- + NEWS | 24 ++ + README | 8 + afs.c | 14 - + audiod.c | 463 +++++++++++++++++++++++++++++++++++++++++----------- + audiod.ggo | 7 + configure.ac | 16 + + index.html | 19 ++ + mysql.c | 17 + + net.c | 8 + recv.c | 42 +++- + scripts/demo-script | 2 + signal.c | 22 ++ + stat.c | 2 + 16 files changed, 542 insertions(+), 166 deletions(-) + +0.2.2 (2005-08-19) "tangential excitation" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Mostly internal changes in this release, but also some new commands +for the mysql database tool. + + o cleanup exec.c, fix para_exec bug + o compile time loglevel (log messages below the given level + won't be compiled in, which reduces the size of the + resulting binaries) + o new log macros that shorten the size of the source code. + o workaround a gcc-4.1 bug (?) that caused send_cred_buffer() + to send only zeros. With this workaround, para_audioc works + again. + o avoid gcc-4 warning: conflicting types for built-in function 'clog' + o new mysql commands: "rm" (remove entry), "mv" (rename entry) "ne" + (new entry), "snp" (set numplayed). Read the manual for more + information. + + INSTALL | 10 - + Makefile.in | 7 - + NEWS | 19 +++ + README | 5 + afs.c | 75 ++++++-------- + audioc.c | 5 + audiod.c | 137 ++++++++++++------------- + client.c | 83 +++++++-------- + command.c | 85 +++++++--------- + configure.ac | 2 + crypt.c | 6 - + daemon.c | 26 ++-- + dbadm.c | 128 ++++++++++++------------ + dopey.c | 9 - + exec.c | 180 ++++++---------------------------- + fade.c | 76 +++++++------- + gui.c | 88 ++++++++-------- + krell.c | 113 ++++++++++----------- + mp3.c | 49 ++++----- + mysql.c | 275 ++++++++++++++++++++++++++++++++++++++++++++-------- + net.c | 44 ++------ + ogg.c | 91 ++++++++--------- + oggdec.c | 2 + para.h | 110 +++++++++++++------- + recv.c | 26 ++-- + scripts/demo-script | 4 + sdl_gui.c | 3 + send.c | 26 ++-- + server.c | 74 ++++++------- + signal.c | 19 +-- + skencil/overview.sk | 34 +++--- + slider.c | 4 + stat.c | 27 ++--- + string.c | 62 +++++++---- + 34 files changed, 1000 insertions(+), 904 deletions(-) + +0.2.1 (2005-08-15) "surreal experience" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Here comes paraslash-0.2.1. It contains a couple of new features and, +surprise, only minor bug fixes. + + o kill noisy mp3 debug message + o cleanup of the build system + o para_server and para_client directly use the crypto routines + of the openssl library rather than invoking the openssl command + line utitlity + o server/audiod: new option --user to switch to the given user + when invoked as root. Read the output of "para_server -h" for + more information. + o gui/sdl_gui: new option --stat_cmd to be used to retrieve the + status. Default: "para_audioc stat" + o sdl_gui: new option --pic_cmd to be used to download the picture. + Default: "para_client pic" + o audiod: 5 slots ought to be enough for everybody + o audiod: new status item: Uptime, kill hup command + + +New files: crypt.c crypt.h + + 1.0 | 2 + FEATURES | 18 +++---- + INSTALL | 37 ++++++++++----- + Makefile.in | 114 +++++++++++++++++++++--------------------------- + NEWS | 38 +++++++++++----- + README | 27 +++++------ + afs.c | 6 -- + audiod.c | 117 ++++++++++++++++++------------------------------- + audiod.ggo | 1 + client.c | 78 +++------------------------------ + command.c | 103 +++++++------------------------------------ + configure.ac | 78 ++++++++++++++++++++++----------- + daemon.c | 44 +++++++++++++++++- + dbadm.c | 7 +- + dopey.c | 14 ++--- + fade.c | 3 - + gui.c | 7 +- + gui.ggo | 3 - + gui_common.c | 7 ++ + gui_theme.c | 122 ++++++++++++++++++++++++++++------------------------ + index.html | 38 ++++++++++------ + mp3.c | 79 +++++++++++++++++---------------- + mp3dec.c | 8 +-- + mysql.c | 14 ++++- + net.c | 3 - + ogg.c | 21 ++++---- + para.h | 11 ++-- + sdl_gui.c | 19 ++++---- + sdl_gui.ggo | 10 ++-- + server.c | 19 -------- + server.ggo | 3 - + server.h | 3 - + slider.c | 19 +++----- + stat.c | 24 +++++----- + string.c | 12 ++++- + 36 files changed, 530 insertions(+), 581 deletions(-) + + +0.2.0 (2005-08-06) "distributed diffusion" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +After several month of increased development activity, paraslash-0.2.0 +has arrived. It contains many new features and is much more +self-contained than the old 0.1.x series. Enjoy! + + + o para_server: fix hang on song change and crash on sighup. + Speed up mysql queries. The DIR_LIKE macro is gone. + o new executables: para_audiod, the local audio daemon that + starts playback (uses SCM_CREDENTIALS socket magic) and + para_audioc, the corresponding client. + o new executables: para_mp3dec/para_oggdec, two really teensy + decoders. para_mp3dec is based on libmad, para_oggdec requires + libvorbisfile. + o ovsend/ovrecv are capable of streaming ogg as well as mp3, so + they are now called para_send and para_recv respectively. + o documentation updates + o para_gui is themable. For now there is the default theme that + looks as before and the simple theme: blue and easy. + o gui: audio streaming is now handled by audiod. Time display shows + playback time rather than streaming time + o slider: update to libzmw-0.2.0 + o para_krell: fix crash on server shutdown + o switch from gzip to bzip2 + +New files: audioc.c audioc.ggo audiod.c audiod.ggo daemon.c gui_theme.c mp3dec.c oggdec.c + recv.c recv.ggo send.c send.ggo stat.c + + 1.0 | 1 + COPYING | 2 + CREDITS | 4 + FEATURES | 24 - + INSTALL | 131 ++++- + Makefile.in | 83 ++- + NEWS | 58 +- + PUBLIC_KEY | 43 + + README | 125 +++-- + README.mysql | 18 + afs.c | 330 +++++++++----- + client.c | 41 - + client.conf.sample | 2 + client.ggo | 5 + command.c | 350 +++++++-------- + configure.ac | 38 + + dopey.c | 2 + exec.c | 242 ++++++---- + fade.c | 43 - + fade.ggo | 2 + gui.c | 1169 +++++++++-------------------------------------------- + gui.ggo | 10 + gui_common.c | 58 +- + index.html | 176 ++++++- + krell.c | 4 + mp3.c | 341 +++++++++------ + mysql.c | 180 ++++---- + net.c | 161 +++++++ + ogg.c | 444 +++++++++++++------- + para.h | 103 ++++ + sdl_gui.c | 708 ++++++++++++++------------------ + sdl_gui.ggo | 2 + server.c | 175 +++---- + server.ggo | 13 + server.h | 20 + slider.c | 160 +++---- + string.c | 63 ++ + time.c | 103 +++- + 38 files changed, 2821 insertions(+), 2613 deletions(-) + + +0.1.7 (2005-04-18) "melting penetration" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The main change in this release is clearly the oggvorbis rewrite, +but there are also lots of smaller changes. If you intend to use both +the mp3 and the ogg plugin, it is recommended to use software mixing, +e.g. the dmix plugin which is provided by ALSA. + + o new executables: para_ovsend and para_ovrecv for sending/receiving + oggvorbis files via rtp. Requires the open rtp library. Get it at + http://www.linphone.org/ortp/ + o rewrite of the ogg_vorbis core code + o configure detects libzmw and, if detected, includes + para_slider to the list of binaries to be built by make + o server stream writers read from their associated fifo rather + than from stdin + o slider: two new sliders, lastplayed and numplayed + o fix nasty double free bug which caused random segfaults in case of + mp3 files with invalid header information + o gui: new command line option: --stream_timeout=seconds to + deactivate a slot if it is idle for that many seconds (default=`5') + o diffstats + +New files: ovrecv.c ovrecv.ggo ovsend.c ovsend.ggo time.c + CREDITS | 2 + FEATURES | 14 - + Makefile.in | 20 + + NEWS | 25 ++ + README | 28 ++ + README.mysql | 18 - + afs.c | 238 ++++++++++++++++------- + autogen.sh | 5 + bash_completion | 2 + command.c | 9 + configure.ac | 115 ++++++++--- + exec.c | 6 + gui.c | 8 + gui.ggo | 1 + krell.c | 14 - + mp3.c | 117 +++++++---- + mysql.c | 4 + ogg.c | 578 +++++++++++++++++++++----------------------------------- + para.h | 9 + server.c | 65 ++++-- + server.ggo | 2 + server.h | 17 + + slider.c | 68 ++++++ + string.c | 11 + + 24 files changed, 794 insertions(+), 582 deletions(-) + + +0.1.6 (2005-03-05) "asymptotic balance" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Only little user-visible changes in this release. Mainly bugfixes and +core code cleanup. This is probably the most stable version ever if you +stick to mp3... + + o fix several memory leaks + o rename default name of mysql database from "music" to "paraslash". + Use para_server's --mysql_database option if you do not want to + rename your old database. + o rework ogg vorbis code + o make update command work on mysql servers with LOCAL_INFILE + disabled + o gui: improved stream I/O (slots) + o simplified audio format API + o para_pob_ogg is gone + +0.1.5 (2004-12-31) "opaque eternity" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Let's slide gently into the new year. + + o new: para_slider (not built automatically, type "make + para_slider" to build). A toy for those who always felt that + creating stream definitions is difficult. See screenshots, + README and FEATURES for more info. + o improved signal handling. Fixes server segfault on SIGHUP + for linux kernels newer than Aug 24 2004 and makes para_gui + race-free. + o reload database tool on SIGHUP + o improved help message for sl + o do not log "broken pipe" messages as errors. They are + perfectly ok. + o fix wrong error message on permission errors + +0.1.4 (2004-12-19) "tunneling transition" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Bugfix release. As expected, 0.1.3 introduced a bunch of new bugs. +Hopefully, most of them got wiped out with this release. Some +enhancements went also in. + + o improved error diagnostics for all commands + o stradd/picadd: overwrite previous contents if entry already + exists, rather than returning errors + o stradd: use current stream if invoked without args + o faster (and hopefully more stable) ogg-vorbis handling + o para_krell: reap children to avoid zombie-flooding in case + no server is running + o si: report also server pid + o server: don't busy-loop if dbtool reports only invalid files. + o gui: CTRL+C works again, fix stream_read command line option + o fix pic_add, hist + o fix mysql dbtool startup in case no database exists + o many small fixes and cleanups + +0.1.3: (2004-12-10) "vanishing inertia" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Starting from this release, the database tools are integrated in the +server binary. This decreases server startup time, reduces code size +and speeds up database commands. However, the layout of the underlying +mysql database changed only slightly and 0.1.3 should be backwards +compatible in that respect. + +Visible changes: + + o If mysql is not detected at compile time, or fails to init + at runtime, fall back to the dopey database tool which should + always work. + o para_dbtool and dbtool.conf are gone. All mysql specific + options are read from server.conf and are prefixed by 'mysql_'. + o new command: cdt (change database tool) + o new command line option: dbtool (choose startup database tool) + o The name of current stream is now stored in the database, + so paraslash remembers its current stream when restarted. + o new command: csp (change stream and play) + o para_gui also reports current database tool and server uptime + + +0.1.2: (2004-11-28) "spherical fluctuation" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Point release before the big dbtool changes go in. + + o dbtool: rename ca to cam (copy all meta data). It now also + copies numplayed and lastplayed time as well as the picture + id. + o fix endless-loop-bug caused by mp3 files with invalid header + +0.1.1: (2004-11-05) "floating atmosphere" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + o gkrellm plugin + o new dbtool command: mbox. Browse your sound-file collection + with your favorite mail reader. + o several small fixes + +0.1.0: (2204-10-22) "rotating cortex" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + o fix logging bug for loglevel > VERBOSE + o fix skip command + o correct timings for vbr mp3s + o modular audio format support + o ogg-vorbis support (experimental) + o new server option: autoplay + +0.0.99: (2004-07-25) "harmonic deviation" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + o rename projectname from icc to paraslash (play, archive, rate + and stream large audio sets happily) + o paraslash is no longer restricted to one particular audio + streaming software + o new dbtool commands (stradd, strq, strdel) for easy stream + managment w/o configuration file. That obsoletes stream_defs + file/config option for dbtool. + o picadd accepts jpeg data from stdin + o new server commands: ps (select previous stream), sc (song change) + o new default pictures for sdl_gui + o gui: new key_map option for binding commands and internal + functions to arbitrary keys, nice help screen, rip out + soundcard/linux specific stuff, avoid noise artefacts while jumping, + show silly logo on startup + o new executables: para_fade for fading volume, para_dbadm for + manipulating attributes + o cdb adds _all_ tables to mysql database + o revised and beautified documentation + o sample dbtool rewritten in C + o autoconf + +0.0.98: (2003-12-26) "incremental smoothness" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + o kick icecast in favour of poc. That removes some races and reduces + core code considerably. + o cbr/vbr is displayed by stat and gui/sdl_gui. New status flags + give finer info on afs' status. + o gui can start decoder (see config options). Further new gui + commands: refresh (^L), jmp (F1-F10) + o gui rereads conf on SIGUSR1 instead of SIGHUP. SIGHUP + terminates gui. This fixes dead instances consuming memory + continuously. + o new dbtool command: verb for sending verbatim sql queries. + o fix pid_list races (by removing pid_list) + o codename funnies + +0.0.97: (2003-10-26) +~~~~~~~~~~~~~~~~~~~~ + o installation prefix now defaults to /usr/local + o new commands for gui: snozze, sleep and reread config + o config file for gui and sdl_gui + o fix problems with filenames containing funny characters + (reported by Thomas Forell) + o improved signal handling for gui, now it rereads conf on SIGHUP + o new dbtool command: cdb (create database) + o switch from argtable to gengetopt + o major code cleanup and speed improvements + o fix several potential buffer overflows + o many small fixes and cleanups + +0.0.96 (2003-08-30) +~~~~~~~~~~~~~~~~~~~ + o easy stream_defs syntax + o sdl_gui can display images associated to the file being played + o Major feature enhancements for icc_gui including dynamic text + placement and the top/bottom window design + o vrfy/clean now also checks for NULL values in attributes as + well as for invalid picture pointers + o fix long outstanding case sensitivity bug + o many small fixes and cleanups + +0.0.95 (2003-06-29) +~~~~~~~~~~~~~~~~~~~ + o sdl gui runs much faster + o new dbtool command: ca (copy attributes) + o count and display number of times the song has been played + o new feature: scoring + o command line options for sdl_gui + o simpler syntax of streams file + o decrease network traffic of stat + o fix zombie bug + o many small fixes and cleanups + +0.0.94 (2003-05-04) +~~~~~~~~~~~~~~~~~~~ + o new server command: ns (next stream) + o new icc_gui command: c (change stream) + o internal mp3info + o stat shows also id3 tag info + o new sdl based gui + o log flodding bug fixed + o many small fixes and cleanups + +0.0.93 (2003-03-28) +~~~~~~~~~~~~~~~~~~~ + o colors for icc_gui + o icc_gui sets volume directly (linux only) + o proper locking that fixes some races + o fix security bug that caused commands to be executed even + with unsufficient permissions + o new command: hup to make all servers reread their configuration file + o icecast meta data streaming + o many small fixes and cleanups diff --git a/PUBLIC_KEY b/PUBLIC_KEY new file mode 100644 index 00000000..671e45da --- /dev/null +++ b/PUBLIC_KEY @@ -0,0 +1,35 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +mQGiBD7zR2oRBACHWxj9A83mTutuXn4Om8pn/wKyBBfMk7+RiF6tcPiwX+cLYNpv +BVN2HDk2QQxzInsU0QIC8SnaCL+BE5s14CjlFbyRdMYTusMDDbEulrsoYUzV+Ut4 +LAdNasCvB03sYBqRwrgy7Qj90mRJOMM8k0s8YoZTTfBFH+oZS8BJUy2bgwCgq4Tp +6pGViIMiZgNC3+xYCOArRPUD/26O3sUErfXH5AOpHv7HKCOS9+Xyx/8Bj7CE6dmz +4sC+rh9cr+cOl6Ux/qupsgB02ZreWHgRVgSEIufilciJKHMv81N1HB4dbqHr4vNu +ooavcj3Ffp2FI2bEhhwE3kzVhHopxa6H2AmgQFx9upXZNoRiBPSKnD/OfbUsIm/R +nzlxA/9wJ5KnP0xdKEHnJT++4c3nlSv4vPl33Oua5nV05g3/MqihdUelLudBSRb4 +bSrNMZF+Fv0zjPsnVrgVdAPbWLPZmL9cJBabEJ6EA3doYNKEJ2u83Dd5S/aTAkH9 +r88xLW51bbtRRJFXdwZx2x/uZQI76M6JuMrzRwhkpbZW70Ar77QfQW5kcmUgTm9s +bCA8bWFhbkBwYXJhc2xhc2gub3JnPoheBBMRAgAeBQJC5Oz+AhsjBgsJCAcDAgMV +AgMDFgIBAh4BAheAAAoJEFraNUAxAJMPKZUAn1eNn9/zydyds+WtXYh4p4L75CDq +AJ4idK9WPfbyH+PQLeVvoMKnJlpqpLQsQW5kcmUgTm9sbCA8bm9sbEBtYXRoZW1h +dGlrLnR1LWRhcm1zdGFkdC5kZT6IWQQTEQIAGQUCPvNHagQLBwMCAxUCAwMWAgEC +HgECF4AACgkQWto1QDEAkw9sqgCfaivNblcXj/CReVkMAUlS1GihCYMAn10hlgFY +dzxtEz7kq0+vHfJx3kQJtCFBbmRyZSBOb2xsIDxtYWFuQHN5c3RlbWxpbnV4Lm9y +Zz6IYQQTEQIAIQIbIwYLCQgHAwIDFQIDAxYCAQIeAQIXgAUCQuTxGAIZAQAKCRBa +2jVAMQCTDwqHAJ4uq5RbbAvddMj9SzfPXOjkXZEMEwCffXrKaf6hYas6LPXn0U8A +E3eKiPS5Ag0EPvNHeRAIAIHxWMwuho61sLeoa0QTnxURcoqC60euJnleMEOQ3bzl +vw4pHFQjI2eww1sUiBCw1MQ0omR3NE346t4J3aNSAcR/RvzxV+xL30l6jvxldbwB +7KNFy0fich5L6rh0f3+ZYmwiHCClx0fGCV3HLEk2jHysB7OgzahY0XCMPOXKAoWA +NCXU3qw/ty/W9TkD/sjv9CixONzuzyMuReeRqG9eLTDIkOA41JxzOGvRqLPbX/tX +lD6ZVDp5+ceyyG6e4QQ3jXUhm4jFiKhqENwpHxPjK+wQFmdV2RHps8i5Y9JM1a2q +oVdk/seG+SyTTq3/odCzEwVEsovBMkKNxn2h2wdgZ9sAAwUH+wfdEZQDW7Hj7qG9 +k1Q96iLvAB3d4jRSmVgn0b+HomBp+KGfpsqWI7qTz4x8Tg+F6+C2MrRFxWYGMVLC +hjM7HH31FMK3MaD5IUHC3/JpsA3S+diUhyM3kqpvyZAlzOv5YdKHG6XDKva8WEgE +9OrEd5Wp8Gr6fRlX3Odb1eBYGfmSaA3y6Qd8iTOFELSzOxsx5uTIPaviACuVZHAO +aksQxVdH+6/TNeBNWRQy/8yaLItlL5gxEs1WmjGoIX65g9mTv2PB1PAN0fC4r5En +vJN3X3DHMlcneaWx2zH0OFYHjInZitAutxAb61EuPBe2W0hYnGzVaZTIN2pfAImm +xY8TcfOIRgQYEQIABgUCPvNHeQAKCRBa2jVAMQCTDztMAJ9+oOTYuDDl2y8frQ4i +WKvqFGIO/ACgjtIdZj4ZlBiAYHArJOlyFYOLjWw= +=5Iez +-----END PGP PUBLIC KEY BLOCK----- diff --git a/README b/README new file mode 100644 index 00000000..967655fc --- /dev/null +++ b/README @@ -0,0 +1,219 @@ +Paraslash README +================ +Paraslash is an acronym for + + Play, archive, rate and stream large audio sets happily + +It contains the following programs: + +- para_server (obligatory): + + This server listens on a specified tcp port and accepts the + usual commands such as play, stop, pause, next. However, there + are many more commands. + + For audio streaming, at least one sender must be activated. + At the moment, paraslash contains two internal senders: + + The http sender is recommended for public streams that can + be played by any player like mpg123, xmms, winamp... + + The ortp sender is recommended for LAN streaming and for + private streams that require authentication. + + It is possible to activate more than one sender simultaneously. + All senders have the same set of commands that allow to + control the access permissions of the stream. + + para_server needs a database tool to work, mainly to determine + which song to stream next. There are two database tools + available: mysql and dopey. The former is recommended as dopey + is only meant as a fallback and as a starting point for people + that want to write their own database tool for paraslash. + + The mysql database tool connects to a mysql server which + holds information on your audio files. It has several unusual + features, see README.mysql for details. + +- para_client (obligatory): + + The client program to connect to para_server. + +- para_recv (optional) + + A command line http/ortp stream grabber. + +- para_filter (optional) + + An filter program that converts from stdin and writes to + stdout. This one is independent from the rest of paraslash, + so it might be useful also for different purposes. + + para_filter combines an mp3 decoder an oggvorbis decoder + and a volume normalzer. New filters can be added easily due + to the modular design. If more than one filter is specified, + the given filters are 'piped' together in-memory, i.e. without + calling any of the read(2)/write(2)/select(2) etc. functions. + +- para_play (optional) + + A small wav/raw player for alsa. + +- para_audiod (optional, but recommended): + + The local daemon that starts playback and collects information + from para_server to be forwarded to local clients. + + para_audiod reads the audio stream from the network if + para_server indicates that there is a stream available. It may + be sent through any of the supported filters (see para_filter + above) before the result is fed to the output software + (default: para_play) which must be capable of reading from + stdin, but is not restricted otherwise. + +- para_audioc (optional, but recommended) + + A small client that can talk to para_audiod. Used to control + para_audiod and to receive status info. It can also be used to + grab the stream at any point in the filter chain. para_audioc + is needed by para_gui, para_sdl_gui and para_krell, see below. + +- para_gui (optional, but recommended): + + Themable ncurses-based gui. It calls para_audioc and presents + the obtained information in an ncurses window. para_gui + provides key-bindings for the most common commands and new + key-bindings can be added easily. + +- para_sdl_gui (optional): + + SDL-based gui. Similar to para_gui but presents its output in + an X window (fullscreen mode is also available) and can display + jpg images on a per song basis. para_sdl_gui provides an input + prompt to enter arbitrary commands. However, it can also be used + non-interactively (e.g. as a screen saver) via the -i switch. + +- para_krell (optional, only useful in conjunction with the mysql dbtool): + + A plugin for gkrellm which shows small pictures of the + current song. It allows you to launch 27 different commands + by clicking in different areas of its picture (9 small squares + x 3 mouse buttons). + +- para_fade (optional): + + A (Linux-only) alarm clock and volume-fader. + +- para_dbadm (optional, only useful in conjunction with the mysql dbtool): + + Very simple curses-based frontend which uses libmenu. Useful + for quickly changing the attributes of the current song + (e.g. from para_gui as an external command). + +- para_slider (optional, only useful in conjunction with the mysql dbtool): + + A small X application which shows a scrollbar for each + attribute defined in the mysql database. It creates a stream + definition from the values of the scrollbars. This allows + to smoothly change the mood of the given stream without any + file editing. + +- bash_completion (optional): + + A small bash script for inclusion in ~/.bashrc. It gives you + command line completion for some paraslash commands. + +REQUIREMENTS: +~~~~~~~~~~~~~ +In any case you need + + - gcc, the gnu compiler collection (shipped with distro): gcc-3 + or newer is prefered, but gcc-2.95 is still supported. Note + that gcc-2.95 may spit out many warnings like unused function + parameters and missing initializers. These are all harmless + and may be ignored. + + - openssl (needed by server, client): usually shipped with + distro, but you might have to install the "development" + package as well + + http://www.openssl.org/ + + - software mixing, e.g. ALSA and the direct mixing plugin (dmix) + +If you want to use the mysql-based dbtool (recommended), you also need + + - mysql-server + - mysql-client + - libmysqlclient + +These are usually shipped with the distro but probably not installed +by default. + +The mp3 decoder of para_filter is based on libmad: + + http://www.underbit.com/products/mad/ + +If you prefer to use the libmad package provided by your distributor, +make sure to install the corresponding development package as well. + +If you want to stream ogg vorbis files you'll need: + + - libogg, libvorbis, libvorbisfile, and a command line ogg vorbis + player, e.g. para_filter or ogg123. + + http://www.xiph.org/downloads/ + +Note that para_audiod still works even if neither mp3 nor ogg support +was compiled in. You'll have to use the --no_default_filters option +in this case (and e.g. "mpg123 -" as the stream write command). + +If you intend to use the optional ortp streamer: + + - libortp + + http://www.linphone.org/ortp/ + + +For the optional SDL-based gui, the following packages must be installed: + + - X (usually shipped with distro) + + http://www.x.org/ + + - libSDL (usually shipped with distro) + + http://www.libsdl.org/index.php + + - SDL_image + + http://www.libsdl.org/projects/SDL_image/ + +For para_slider, the zero memory widget library is neccessary. Get it at + + http://www710.univ-lyon1.fr/~exco/ZMW/ + +Finally, para_krell needs + + - gtk2 + + http://www.gtk.org/ + + - gkrellm2 + + http://members.dslextreme.com/users/billw/gkrellm/gkrellm.html + +LICENSE: +~~~~~~~~ +Distribution of paraslash is covered by the GNU GPL. See file COPYING. + +LIMITATIONS: +~~~~~~~~~~~~ +The mysql database tool assumes that the basenames of your audio files +are unique. If this is not the case, don't use this database tool, +rename your files, or create your own database tool. + +THE AUTHOR: +~~~~~~~~~~~ +Author: Andre Noll +Comments and bug reports are welcome. diff --git a/README.mysql b/README.mysql new file mode 100644 index 00000000..d2a27e72 --- /dev/null +++ b/README.mysql @@ -0,0 +1,325 @@ +README.mysql +============ + +This file describes how to use the mysql database tool which comes +with the paraslash package. + +It assumes you have already installed mysql and paraslash as described +in INSTALL, so read README and INSTALL before proceeding. + +First of all, make sure that + + - mysqld is running + - para_server is running and compiled with mysql support (type + "para_client si" to find out) + - the user who runs para_client has the paraslash DB_WRITE and DB_READ + permissions set in server.users + - the user who runs para_server has create privileges on the mysql + server. + +Remember: If something doesn't work as expected, look at the server log file +and/or increase output verbosity by using the -l switch for server and client. + + +Specify mysql data (port, passwd,...) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Type + + para_server -h + +and look at the mysql options. You may either specify these options +in ~/.paraslash/server.conf or directly at the command line (not +recommended for passwd option). Don't forget to do + + chmod 600 ~/.paraslash/server.conf + +as this file contains the mysql passwd. To make these changes take +effect you'll need to do + + para_client hup + +Or, restart the server. + +Switch to the mysql dbtool +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Actually, the mysql database tool should already be selected (just +ignore the warning about the missing database). To verify that it is +indeed activated, type + + para_client cdt + +which prints the name of the current database tool. If the dopey +dbtool is still selected, try + + para_client cdt mysql + +If this doesn't work either, it means that some required config options +were not specified (check the log for more info) or that para_server +was built without mysql support. Type + + para_client si + +to find out. If mysql is not mentioned as a supported database tool, +you'll have to recompile. + + +Create a new database +~~~~~~~~~~~~~~~~~~~~~ + +Once the mysql database tool is selected, create the database: + + para_client cdb + para_client cdt mysql + +The second command forces para_server to re-init the mysql dbtool. +Check the log. There should not be any warnings or errors. + + +Fill your database with content +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + para_client upd + +If this command fails, it most likely means the audio file directory +(given in the server configuration file) does not exist, is empty, +not readable, or contains different files with identical basenames. Fix +this problem before proceeding. + +The command + + para_client ls + +prints the list of all files known by the mysql dbtool. If the list +is empty, double check the mysql_audio_file_dir option. + + +Create a stream which selects all songs +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To keep it simple, let's only define the stream "all_songs". See below for +advanced stream usage. + + para_client stradd all_songs < /dev/null + para_client sl 10 all_songs + +The latter command should show you ten filenames. + + +Change to the all_songs stream +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + para_client cs all_songs + +You should now be able to start streaming with + + para_client play + + +Attribute usage +~~~~~~~~~~~~~~~ + +An attribute is simply a bit which can be set for each sound file +individually. You may have as many attributes as you like. A new +attribute "test" is created by + + para_client na test + +and + para_client laa + +lists all available attributes. You can set the "test" attribute for +the current song by executing + + para_client sa test+ + +or for any particular song by + + para_client sa test+ filename + +You can unset the attribute "test" for the current song with + + para_client sa test- + +and you can drop the test attribute entirely from the database with + + para_client da test + +Stream usage +~~~~~~~~~~~~ + +A stream is a pair of expressions in terms of attributes and other +meta data contained in the database. The first, boolian, expression +determines the set of songs which are permitted in this stream. The +second, integer, expression determines the order in which permitted +songs are going to be fed to the audio file sender(s). + +To create a new stream called "my_stream", put arbitrary many (including +none) accept or deny lines and one or zero score lines into some +temporary file, say tmpfile. An accept/deny/score line consists of +an identifier ("accept:", "deny:", or "score:"), followed by an +expression. The command + + para_client stradd my_stream < tmpfile + +adds the stream "my_stream" to dbtool's stream database. + +If the stream definition is really short, you may also just pipe it to +the client rather than using temporary files. Like this: + + echo "$MYSTREAMDEF" | para_client stradd my_stream + + +Example: + + Assume you already have an attribute "test" and you'd like to + to restrict the set of songs being played to those having the + "test" attribute set. Define a new stream "only_test" by + + echo 'accept: IS_SET(test)' | para_client stradd only_test + + Then, after switching to the "only_test" stream with + + para_client cs only_test + + only the desired songs are going to be played. + +There is no need to keep the temporary files containing the stream +definition since you can always query the database to get it back: + + para_client strq only_test + +The accept/deny expressions are used to find out which songs are +permitted. The following four cases are all possible and valid: + + o Neither accept nor deny lines: This selects all songs. + + o Only accept lines: Songs that match at least one accept + expression are accepted, all others are denied: + + accept_expr1 or accept_expr2 or ... + + o Only deny lines: Songs that match at least one deny expression are + denied, all others are accepted: + + not (deny_expr1 or deny_expr2 ...) + + o Both accept and deny lines: A song is accepted if it matches + at least one accept expression, but no deny expression, i.e. + + (accept_expr1 or accept_expr2 or ..) and not + (deny_expr1 or deny_expr2 ..) + +The command + + para_client streams + +lists all available streams and + + para_client strdel streamname + +removes the stream "streamname". + +There are more sophisticated ways to define a stream than just using +one IS_SET() macro as in the example above. Of course, IS_SET(foo) +is true for a given audio file if and only if it has the attribute +"foo" set. Here are some more macros you can use: + + o IS_N_SET(attr): True if attribute attr is not set + + o NAME_LIKE(string): True if basename is like (in the sense + of mysql) "string" + + o LASTPLAYED(): Expands to number of minutes that are gone + since this audio file has been played (by paraslash). + + o NUMPLAYED(): Expands to number of times, the file has + been played. + + o PICID(): Expands to the number of the picture which is + associated with this song. + +To give a real-life example, suppose you have already added the +attributes "pop", "rock" with the "na" command. Assume also that you +have set these attributes for some or all of your songs having the +corresponding properties. + +If you like to be waked up in the morning by poprock songs, but you +have some strange feeling telling you that just a few seconds of +Madonna's voice will be enough to mess up your whole day, just write +the lines + + accept: IS_SET(pop) and IS_SET(rock) + deny: NAME_LIKE(%Madonna%) + +to some temporary file "tmp" and do + + para_client stradd wake < tmp + +You can then switch to the new stream with + + para_client cs wake + +or you can let cron do this for you on a daily basis.. + +Accept/deny lines affect only the set of admissible songs, but not +the order in which these songs are played. That's where the score +expression comes into play. + +Scoring +~~~~~~~ +You may put a single score line anywhere in the stream definition. If +omitted, the default scoring rule specified in the configuration file +applies. If there is no default scoring rule in the config file either, +the compiled in default is going to be used (see para_server -h). + +Simple examples of scoring rules (either specified in a stream +definition or as the default scoring rule in the config file) include: + + LASTPLAYED()/1440 + +This means that each song's score is just the number of days that went +by since this song has been played (one day is 1440 minutes). This +is fine in many cases since the dbtool then always chooses that +admissible song, which wasn't played for the longest time. + +However, one disadvantage of this scoring sheme is that new songs, +once played, are going to be deferred for a possibly very long period +depending on the size of your collection of (admissible) songs. Hence +the following scoring rule comes into mind: + + score: -NUMPLAYED() + +since this gives newer songs, i.e. songs to which you haven't listen to +that often, a higher score than older songs you already know by heart. + +You can also use a combination of these two methods: + + score: LASTPLAYED()/1440 - 10 * NUMPLAYED() + +which subtracts 10 score points for each time paraslash has played +this song. + +Another useful feature for scoring is due to the fact that "true" +expands to one and "false" to zero. So you can also use the +IS_SET/IS_N_SET/NAME_LIKE macros in a score line to give +your favorite band "bar" some extra points: + + score: 40 * IS_SET(foo) + 20 * NAME_LIKE(%bar%) + LASTPLAYED()/1440 + + +Pictures +~~~~~~~~ + +dbtool can also magage images that, when associated with certain songs, +can be displayed by para_sdl_gui and para_krell when one of these songs +is playing. It is also possible to just retrieve the current image via + + para_client pic > filename + +in order to feed it to your favorite tool. Try + + para_client help | grep ^pic + +and read the online help of the shown commands for more information. diff --git a/SFont.c b/SFont.c new file mode 100644 index 00000000..407ef990 --- /dev/null +++ b/SFont.c @@ -0,0 +1,259 @@ +/* + * Copyright (C) Karl Bartel 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 +#include "SFont.h" +#include /* exit */ +#include /* 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 "); + if (X >= Surface->w) + puts("SFONT ERROR: x too big in GetPixel. Report this to " + ""); + Bpp = Surface->format->BytesPerPixel; + bits = ((Uint8 *) Surface->pixels) + Y * Surface->pitch + X * Bpp; + + /* Get the pixel */ + switch (Bpp) { + case 1: + return *((Uint8 *) Surface->pixels + Y * Surface->pitch + X); + break; + case 2: + return *((Uint16 *) Surface->pixels + Y * Surface->pitch / 2 + + X); + break; + case 3:{ /* Format/endian independent */ + Uint8 r, g, b; + r = *((bits) + Surface->format->Rshift / 8); + g = *((bits) + Surface->format->Gshift / 8); + b = *((bits) + Surface->format->Bshift / 8); + return SDL_MapRGB(Surface->format, r, g, b); + } + break; + case 4: + return *((Uint32 *) Surface->pixels + Y * Surface->pitch / 4 + + X); + break; + } + + return -1; +} + +void InitFont2(SFont_FontInfo * Font) +{ + int x = 0, i = 0; + + if (!Font->Surface) { + printf("The font has not been loaded!\n"); + exit(EXIT_FAILURE); + } + + if (SDL_MUSTLOCK(Font->Surface)) + SDL_LockSurface(Font->Surface); + + while (x < Font->Surface->w) { + if (GetPixel(Font->Surface, x, 0) == + SDL_MapRGB(Font->Surface->format, 255, 0, 255)) { + Font->CharPos[i++] = x; + while ((x < Font->Surface->w - 1) + && (GetPixel(Font->Surface, x, 0) == + SDL_MapRGB(Font->Surface->format, 255, 0, + 255))) + x++; + Font->CharPos[i++] = x; + } + x++; + } + if (SDL_MUSTLOCK(Font->Surface)) + SDL_UnlockSurface(Font->Surface); + + Font->h = Font->Surface->h; + SDL_SetColorKey(Font->Surface, SDL_SRCCOLORKEY, + GetPixel(Font->Surface, 0, Font->Surface->h - 1)); +} + +#if 0 +void InitFont(SDL_Surface * Font) +{ + InternalFont.Surface = Font; + InitFont2(&InternalFont); +} + +#endif + + + +void PutString2(SDL_Surface * Surface, SFont_FontInfo * Font, int x, int y, + char *text) +{ + int ofs; + int i = 0; + SDL_Rect srcrect, dstrect; + + while (text[i] != '\0') { + if (text[i] == ' ') { + x += Font->CharPos[2] - Font->CharPos[1]; + i++; + } else { + ofs = ((unsigned char) text[i] - 33) * 2 + 1; + srcrect.w = dstrect.w = (Font->CharPos[ofs + 2] + + Font->CharPos[ofs + 1]) / 2 + - (Font->CharPos[ofs] + Font->CharPos[ofs - 1]) + / 2; + srcrect.h = dstrect.h = Font->Surface->h - 1; + srcrect.x = + (Font->CharPos[ofs] + Font->CharPos[ofs - 1]) / 2; + srcrect.y = 1; + dstrect.x = + x - (float) (Font->CharPos[ofs] - + Font->CharPos[ofs - 1]) / 2; + dstrect.y = y; + SDL_BlitSurface(Font->Surface, &srcrect, Surface, + &dstrect); + x += Font->CharPos[ofs + 1] - Font->CharPos[ofs]; + i++; + } + } +} +#if 0 +void PutString(SDL_Surface * Surface, int x, int y, char *text) +{ + PutString2(Surface, &InternalFont, x, y, text); +} + +#endif +int TextWidth2(SFont_FontInfo * Font, char *text) +{ + int ofs = 0; + int i = 0, x = 0; + + while (text[i] != '\0') { + if (text[i] == ' ') { + x += Font->CharPos[2] - Font->CharPos[1]; + i++; + } else { + ofs = ((unsigned char) text[i] - 33) * 2 + 1; + x += Font->CharPos[ofs + 1] - Font->CharPos[ofs]; + i++; + } + } + return x; +} + +#if 0 +int TextWidth(char *text) +{ + return TextWidth2(&InternalFont, text); +} +#endif + +void XCenteredString2(SDL_Surface * Surface, SFont_FontInfo * Font, int y, + char *text) +{ + PutString2(Surface, Font, Surface->w / 2 - TextWidth2(Font, text) / 2, + y, text); +} + +#if 0 +void XCenteredString(SDL_Surface * Surface, int y, char *text) +{ + XCenteredString2(Surface, &InternalFont, y, text); +} +#endif + +void SFont_InternalInput(SDL_Surface * Dest, SFont_FontInfo * Font, int x, + int y, int PixelWidth, char *text) +{ + SDL_Event event; + int ch = -1, blink = 0; + long blinktimer = 0; + SDL_Surface *Back; + SDL_Rect rect; + int previous; + + Back = SDL_AllocSurface(Dest->flags, + Dest->w, + Font->h, + Dest->format->BitsPerPixel, + Dest->format->Rmask, + Dest->format->Gmask, Dest->format->Bmask, 0); + rect.x = 0; + rect.y = y; + rect.w = Dest->w; + rect.h = Font->Surface->h; + SDL_BlitSurface(Dest, &rect, Back, NULL); + PutString2(Dest, Font, x, y, text); + SDL_UpdateRects(Dest, 1, &rect); + + /* start input */ + previous = SDL_EnableUNICODE(1); + blinktimer = SDL_GetTicks(); + while (ch != SDLK_RETURN) { + if (event.type == SDL_KEYDOWN) { + ch = event.key.keysym.unicode; + if (((ch > 31) || (ch == '\b')) && (ch < 128)) { + if ((ch == '\b') && (strlen(text) > 0)) + text[strlen(text) - 1] = '\0'; + else if (ch != '\b') + sprintf(text, "%s%c", text, ch); + if (TextWidth2(Font, text) > PixelWidth) + text[strlen(text) - 1] = '\0'; + SDL_BlitSurface(Back, NULL, Dest, &rect); + PutString2(Dest, Font, x, y, text); + SDL_UpdateRects(Dest, 1, &rect); + SDL_WaitEvent(&event); + } + } + if (SDL_GetTicks() > blinktimer) { + blink = 1 - blink; + blinktimer = SDL_GetTicks() + 500; + if (blink) { + PutString2(Dest, Font, + x + TextWidth2(Font, text), y, "|"); + SDL_UpdateRects(Dest, 1, &rect); + } else { + SDL_BlitSurface(Back, NULL, Dest, &rect); + PutString2(Dest, Font, x, y, text); + SDL_UpdateRects(Dest, 1, &rect); + } + } + SDL_Delay(1); + SDL_PollEvent(&event); + } + text[strlen(text)] = '\0'; + SDL_FreeSurface(Back); + /* restore the previous state */ + SDL_EnableUNICODE(previous); +} + +void SFont_Input2(SDL_Surface * Dest, SFont_FontInfo * Font, int x, int y, + int PixelWidth, char *text) +{ + SFont_InternalInput(Dest, Font, x, y, PixelWidth, text); +} diff --git a/SFont.h b/SFont.h new file mode 100644 index 00000000..acdae2aa --- /dev/null +++ b/SFont.h @@ -0,0 +1,59 @@ +/************************************************************************ +* SFONT - SDL Font Library by Karl Bartel * +* * +* 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 + +#ifdef __cplusplus +extern "C" { +#endif + +// Delcare one variable of this type for each font you are using. +// To load the fonts, load the font image into YourFont->Surface +// and call InitFont( YourFont ); +typedef struct { + SDL_Surface *Surface; + int CharPos[512]; + int h; +} SFont_FontInfo; + +// Initializes the font +// Font: this contains the suface with the font. +// The font must be loaded before using this function. +void InitFont (SDL_Surface *Font); +void InitFont2(SFont_FontInfo *Font); + +// Blits a string to a surface +// Destination: the suface you want to blit to +// text: a string containing the text you want to blit. +void PutString (SDL_Surface *Surface, int x, int y, char *text); +void PutString2(SDL_Surface *Surface, SFont_FontInfo *Font, int x, int y, char *text); + +// Returns the width of "text" in pixels +int TextWidth(char *text); +int TextWidth2(SFont_FontInfo *Font, char *text); + +// Blits a string to with centered x position +void XCenteredString (SDL_Surface *Surface, int y, char *text); +void XCenteredString2(SDL_Surface *Surface, SFont_FontInfo *Font, int y, char *text); + +// Allows the user to enter text +// Width: What is the maximum width of the text (in pixels) +// text: This string contains the text which was entered by the user +void SFont_Input ( SDL_Surface *Destination, int x, int y, int Width, char *text); +void SFont_Input2( SDL_Surface *Destination, SFont_FontInfo *Font, int x, int y, int Width, char *text); + +#ifdef __cplusplus +} +#endif + +#endif /* SFONT_H */ diff --git a/afs.c b/afs.c new file mode 100644 index 00000000..cb6c3882 --- /dev/null +++ b/afs.c @@ -0,0 +1,479 @@ +/* + * Copyright (C) 1997-2006 Andre Noll + * + * 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 /* gettimeofday */ +#include "server.cmdline.h" +#include "db.h" +#include "server.h" +#include "afs.h" +#include "send.h" +#include "error.h" +#include "string.h" + +extern const char *status_item_list[]; + +static struct timeval announce_tv; +static struct timeval data_send_barrier; +static struct timeval eof_barrier; + +extern struct misc_meta_data *mmd; +extern struct dbtool dblist[]; +extern struct sender senders[]; +extern struct gengetopt_args_info conf; + +static FILE *audio_file = NULL; + +#if 1 + void mp3_init(void *); +#endif + +#ifdef HAVE_OGGVORBIS + void ogg_init(void *); +#endif + +/** + * the list of supported audio formats + */ +struct audio_format afl[] = { +#if 1 + { + .name = "mp3", + .init = mp3_init, + }, +#endif +#ifdef HAVE_OGGVORBIS + { + .name = "ogg", + .init = ogg_init, + }, +#endif + { + .name = NULL, + } +}; + + +/** + * check if audio file sender is playing + * + * \return greater than zero if playing, zero otherwise. + * + */ +unsigned int afs_playing(void) +{ + return mmd->new_afs_status_flags & AFS_PLAYING; +} + +/** + * check if 'next' flag is set afs_status_flags + * + * \return greater than zero if set, zero if not. + * + */ +unsigned int afs_next(void) +{ + return mmd->new_afs_status_flags & AFS_NEXT; +} + +/** + * check if a reposition request is pending + * + * \return greater than zero if true, zero otherwise. + * + */ +unsigned int afs_repos(void) +{ + return mmd->new_afs_status_flags & AFS_REPOS; +} + +/** + * check if audio file sender is paused + * + * \return greater than zero if paused, zero otherwise. + * + */ +unsigned int afs_paused(void) +{ + return !(mmd->new_afs_status_flags & AFS_NEXT) + && !(mmd->new_afs_status_flags & AFS_PLAYING); +} + +/** + * get the name of the given audio format + * \param i the audio format number + * + * This returns a pointer to statically allocated memory so it + * must not be freed by the caller. + */ +const char *audio_format_name(int i) +{ + return i >= 0? afl[i].name : "(none)"; +} + +/** + * initialize the audio file sender + * + * Call the init functions of all supported audio format handlers and + * initialize all supported senders. + */ +void afs_init(void) +{ + int i; + char *hn = para_hostname(), *home = para_homedir(); + + PARA_DEBUG_LOG("supported audio formats: %s\n", + SUPPORTED_AUDIO_FORMATS); + for (i = 0; afl[i].name; i++) { + PARA_NOTICE_LOG("initializing %s handler\n", + afl[i].name); + afl[i].init(&afl[i]); + } + ms2tv(conf.announce_time_arg, &announce_tv); + PARA_INFO_LOG("announce timeval: %lu:%lu\n", announce_tv.tv_sec, announce_tv.tv_usec); + for (i = 0; senders[i].name; i++) { + PARA_NOTICE_LOG("initializing %s sender\n", senders[i].name); + senders[i].init(&senders[i]); + } + free(hn); + free(home); +} + +static int get_file_info(int i) +{ + return afl[i].get_file_info(audio_file, mmd->audio_file_info, + &mmd->chunks_total, &mmd->seconds_total); +} + +/* + * guess the audio format judging from filename + * \param name the filename + * + * \return This function returns -1 if it has no idea what kind of audio + * file this might be. Otherwise the (non-negative) number of the audio format + * is returned. + */ +static int guess_audio_format(const char *name) +{ + + int i, len1 = strlen(name), len2; + + for (i = 0; afl[i].name; i++) { + len2 = strlen(afl[i].name); + if (len1 < len2) + continue; + if (!strncasecmp(name + (len1 - len2), afl[i].name, len2)) { + PARA_DEBUG_LOG("might be %s\n", afl[i].name); + return i; + } + } + return -1; +} + +static int get_audio_format(int omit) +{ + int i; + + for (i = 0; afl[i].name; i++) { + if (i == omit || !afl[i].get_file_info) + continue; + rewind(audio_file); + if (get_file_info(i) > 0) + return i; + rewind(audio_file); + } + return -E_AUDIO_FORMAT; +} + +/* + * upddate shared mem + */ +static int update_mmd(void) +{ + int i; + struct stat file_status; + + i = guess_audio_format(mmd->filename); + if (i < 0 || get_file_info(i) < 0) + i = get_audio_format(i); + if (i < 0) + return i; + mmd->audio_format = i; + mmd->chunks_sent = 0; + mmd->current_chunk = 0; + mmd->offset = 0; + if (fstat(fileno(audio_file), &file_status) == -1) + return -E_FSTAT; + mmd->size = file_status.st_size; + mmd->mtime = file_status.st_mtime; + mmd->events++; + PARA_NOTICE_LOG("next audio file: %s\n", mmd->filename); + return 1; +} + +static void get_song(void) +{ + char **sl = dblist[mmd->dbt_num].get_audio_file_list(10); + int i; + + if (!sl) + goto err_out; + for (i = 0; sl[i]; i++) { + struct timeval now; + PARA_INFO_LOG("trying %s\n", sl[i]); + if (strlen(sl[i]) >= _POSIX_PATH_MAX) + continue; + audio_file = fopen(sl[i], "r"); + if (!audio_file) + continue; + strcpy(mmd->filename, sl[i]); + if (update_mmd() < 0) { + fclose(audio_file); + audio_file = NULL; + continue; + } + mmd->num_played++; + if (dblist[mmd->dbt_num].update_audio_file) + dblist[mmd->dbt_num].update_audio_file(sl[i]); + PARA_DEBUG_LOG("%s", "success\n"); + mmd->new_afs_status_flags &= (~AFS_NEXT); + gettimeofday(&now, NULL); + tv_add(&now, &announce_tv, &data_send_barrier); + + goto free; + } + PARA_ERROR_LOG("%s", "no valid files found\n"); +err_out: + mmd->new_afs_status_flags = AFS_NEXT; +free: + if (sl) { + for (i = 0; sl[i]; i++) + free(sl[i]); + free(sl); + } +} + +static int chk_barrier(const char *bname, const struct timeval *now, + const struct timeval *barrier, struct timeval *diff, int log) +{ + long ms; + + if (tv_diff(now, barrier, diff) > 0) + return 1; + ms = tv2ms(diff); + if (log && ms) + PARA_DEBUG_LOG("%s barrier: %lims left\n", bname, ms); + return -1; +} + +static void afs_next_chunk_time(struct timeval *due) +{ + struct timeval tmp; + + tv_scale(mmd->chunks_sent, &afl[mmd->audio_format].chunk_tv, &tmp); + tv_add(&tmp, &mmd->stream_start, due); +} + +/* + * != NULL: timeout for next chunk + * NULL: nothing to do + */ +static struct timeval *afs_compute_timeout(void) +{ + static struct timeval the_timeout; + struct timeval now, next_chunk; + + if (afs_next() && mmd->audio_format >= 0) { + /* only sleep a bit, nec*/ + the_timeout.tv_sec = 0; + the_timeout.tv_usec = 100; + return &the_timeout; + } + gettimeofday(&now, NULL); + if (chk_barrier("eof", &now, &eof_barrier, &the_timeout, 1) < 0) + return &the_timeout; + if (chk_barrier("data send", &now, &data_send_barrier, + &the_timeout, 1) < 0) + return &the_timeout; + if (mmd->audio_format < 0 || !afs_playing() || !audio_file) + return NULL; + afs_next_chunk_time(&next_chunk); + if (chk_barrier(afl[mmd->audio_format].name, &now, &next_chunk, + &the_timeout, 0) < 0) + return &the_timeout; + /* chunk is due or bof */ + the_timeout.tv_sec = 0; + the_timeout.tv_usec = 0; + return &the_timeout; +} + +static void afs_eof(struct audio_format *af) +{ + struct timeval now; + int i; + char *tmp; + + if (!af || !audio_file) { + for (i = 0; senders[i].name; i++) + senders[i].shutdown_clients(); + return; + } + gettimeofday(&now, NULL); + tv_add(&af->eof_tv, &now, &eof_barrier); + af->close_audio_file(); + audio_file = NULL; + mmd->audio_format = -1; + af = NULL; + mmd->chunks_sent = 0; + mmd->offset = 0; + mmd->seconds_total = 0; + tmp = make_message("%s:\n%s:\n%s:\n", status_item_list[SI_AUDIO_INFO1], + status_item_list[SI_AUDIO_INFO2], status_item_list[SI_AUDIO_INFO3]); + strcpy(mmd->audio_file_info, tmp); + free(tmp); + tmp = make_message("%s:\n%s:\n%s:\n", status_item_list[SI_DBINFO1], + status_item_list[SI_DBINFO2], status_item_list[SI_DBINFO3]); + strcpy(mmd->dbinfo, tmp); + free(tmp); + mmd->filename[0] = '\0'; + mmd->size = 0; + mmd->events++; +} + +/** + * compute the timeout for para_server's main select-loop + * + * This function gets called from para_server to determine the timeout value + * for its main select loop. + * + * Before the timeout is computed, the current afs status flags are evaluated + * and acted upon by calling appropriate functions from the lower layers. + * Possible actions include + * + * - request a new file list from the current dabase tool (audio file change) + * - shutdown of all senders (stop/pause command) + * - repositioning of the stream (ff/jmp command) + * + * \return A pointer to a struct timeval containing the timeout for the next + * chunk of data to be sent, or NULL if we're not sending right now. + */ +struct timeval *afs_preselect(void) +{ + struct audio_format *af = NULL; + int i, format; + struct timeval *ret; +again: + format = mmd->audio_format; + if (format >= 0) + af = afl + format; + else + for (i = 0; senders[i].name; i++) + senders[i].shutdown_clients(); + if (afs_next() && af) { + afs_eof(af); + return afs_compute_timeout(); + } + if (afs_paused() || afs_repos()) { + for (i = 0; senders[i].name; i++) + senders[i].shutdown_clients(); + if (af) { + struct timeval now; + if (!afs_paused() || mmd->chunks_sent) { + gettimeofday(&now, NULL); + tv_add(&af->eof_tv, &now, &eof_barrier); + } + if (afs_repos()) + tv_add(&now, &announce_tv, &data_send_barrier); + if (mmd->new_afs_status_flags & AFS_NOMORE) + mmd->new_afs_status_flags = AFS_NEXT; + } + mmd->chunks_sent = 0; + } + if (af && afs_repos() && mmd->current_chunk != mmd->repos_request) + af->reposition_stream(mmd->repos_request); + if (afs_repos()) { + mmd->new_afs_status_flags &= ~(AFS_REPOS); + mmd->current_chunk = mmd->repos_request; + } + ret = afs_compute_timeout(); + if (!ret && !audio_file && afs_playing() && + !(mmd->new_afs_status_flags & AFS_NOMORE)) { + PARA_DEBUG_LOG("%s", "ready and playing, but no audio file\n"); + get_song(); + goto again; + } + return ret; +} + +/** + * afs_send_chunk - paraslash's main sending function + * + * This function gets called from para_server as soon as the next chunk of + * data should be pushed out. It first calls the read_chunk() function of + * the current audio format handler to obtain a pointer to the data to be + * sent out as well as its length. This information is then passed to each + * supported sender's send() function which does the actual sending. + * + * Return value: Positive return value on success, zero on eof and negative + * on errors. + */ + +void afs_send_chunk(void) +{ + int i; + struct audio_format *af; + char *buf; + ssize_t ret; + struct timeval now, due; + + if (mmd->audio_format < 0 || !audio_file || !afs_playing()) + return; + af = &afl[mmd->audio_format]; + gettimeofday(&now, NULL); + afs_next_chunk_time(&due); + if (tv_diff(&due, &now, NULL) > 0) + return; + buf = af->read_chunk(mmd->current_chunk, &ret); + mmd->new_afs_status_flags &= ~(AFS_NEXT | AFS_REPOS); + if (!buf) { + if (ret < 0) + mmd->new_afs_status_flags = AFS_NEXT; + else + mmd->new_afs_status_flags |= AFS_NEXT; + afs_eof(af); + return; + } + if (!mmd->chunks_sent) { + struct timeval tmp; + gettimeofday(&mmd->stream_start, NULL); + tv_scale(mmd->current_chunk, &af->chunk_tv, &tmp); + mmd->offset = tv2ms(&tmp); + mmd->events++; + } + for (i = 0; senders[i].name; i++) + senders[i].send(af, mmd->current_chunk, + mmd->chunks_sent, buf, ret); + mmd->new_afs_status_flags |= AFS_PLAYING; + mmd->chunks_sent++; + mmd->current_chunk++; +} diff --git a/afs.h b/afs.h new file mode 100644 index 00000000..f1dc9b94 --- /dev/null +++ b/afs.h @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2005-2006 Andre Noll + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ + +/** \file afs.h functions and structures for audio format handling (para_server) */ + +/** \cond */ +#ifdef HAVE_OGGVORBIS +#define OV_AUDIO_FORMAT " ogg" +#define OV_AUDIO_FORMAT_ARRAY , "ogg" +#else +#define OV_AUDIO_FORMAT "" +#define OV_AUDIO_FORMAT_ARRAY +#endif + +#define SUPPORTED_AUDIO_FORMATS "mp3" OV_AUDIO_FORMAT +#define SUPPORTED_AUDIO_FORMATS_ARRAY "mp3" OV_AUDIO_FORMAT_ARRAY, NULL + + +/* status flags */ +#define AFS_NOMORE 1 +#define AFS_NEXT 2 +#define AFS_REPOS 4 +#define AFS_PLAYING 8 +#define DBT_CHANGE 16 +/** \endcond */ + +/** + * structure for audio format handling + * + * There's exactly one such struct for each supported audio format. Initially, + * only \a name and \a init are defined. During the startup process, + * para_server calls the \a init function of each audio format handler which is + * expected to fill in all the other function pointers. + */ +struct audio_format { +/** + * + * + * name of the audio format + */ +const char *name; +/** + * + * + * pointer to the audio format handler's init function + * + * Must initialize all function pointers and is assumed to succeed. + */ +void (*init)(void*); +/** + * + * + * period of time between sending data chunks +*/ +struct timeval chunk_tv; /* length of one chunk of data */ +/** + * + * + * end of file timeout - do not load new audio file until this time + * +*/ +struct timeval eof_tv; /* timeout on eof */ +/** + * + * + * Pointer to the optional get-header function. + * + * This is called from a sender in case a new client connects in the middle of + * the stream. The audio format handler may set this to NULL to indicate that + * this audio format does not need any special header treatment. If non-NULL, + * the function it points to must return a pointer to a buffer holding the + * current audio file header, together with the header length. +*/ +char *(*get_header_info)(int *header_len); +/** + * + * + * check if this audio format handler can handle the file + * + * This is a pointer to a function returning whether a given file is valid for + * this audio format. A negative return value indicates that this audio format + * handler did not recognize the given file. On success, the function is + * expected to return a positive value and to fill in \arg info_str, \arg + * chunks and \arg seconds appropriately. +*/ +int (*get_file_info)(FILE *audio_file, char *info_str, + long unsigned *chunks, int *seconds); +/** + * + * + * cleanup function of this audio format handler + * + * This close function should deallocate any resources + * associated with the current audio file. In particular, it is responsible + * for closing the file handle. It is assumed to succeed. +*/ +void (*close_audio_file)(void); +/** + * + * + * jump to another position in the current audio file + * + * This is called if a client issued the ff or jmp command with \a request + * being the number of the next chunk that should be sent out. Must return a + * positive value on success and a negative value on errors. +*/ +int (*reposition_stream)(long unsigned request); +/** + * + * + * function responsible for reading one data chunk. + * + * \a read_chunk() must return a pointer to the next chunk of data that should + * be sent out, or \p NULL on errors or if the end of the file was encountered. + * + * If it returns non-NULL, \a len must contain the length of the returned + * buffer (which may be zero if nothing has to be sent for some reason). + * Otherwise, \a len is used to distinguish between the eof and the error case: + * It must be zero in the eof case, or negative if an error occcured. +*/ +char * (*read_chunk)(long unsigned chunk_num, ssize_t *len); +}; + +void afs_init(void); +void afs_send_chunk(void); +struct timeval *afs_preselect(void); +const char *audio_format_name(int); +unsigned int afs_playing(void); +unsigned int afs_next(void); +unsigned int afs_repos(void); +unsigned int afs_paused(void); + diff --git a/audioc.c b/audioc.c new file mode 100644 index 00000000..23f67fbb --- /dev/null +++ b/audioc.c @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2005-2006 Andre Noll + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ + +/** \file audioc.c the client program used to connect to para_audiod */ + +#include "audioc.cmdline.h" +#include "para.h" +#include "net.h" +#include "string.h" + +struct gengetopt_args_info conf; +char *tmpfifo; +enum {E_SYNTAX, E_READ, E_WRITE, E_SOCKET, E_INIT_SOCK_ADDR, E_CONNECT, E_CREDENTIALS, E_SELECT, E_OVERRUN}; + + +void para_log(__unused int ll, __unused char* fmt,...) /* no logging */ +{ +} + +/* audioc does not use encryption */ +void (*crypt_function_recv)(unsigned long len, const unsigned char *indata, unsigned char *outdata) = NULL; +void (*crypt_function_send)(unsigned long len, const unsigned char *indata, unsigned char *outdata) = NULL; + +static char *concat_args(const int argc, char * const *argv) +{ + int i; char *buf = NULL; + for (i = 0; i < argc; i++) { + buf = para_strcat(buf, argv[i]); + if (i != argc - 1) + buf = para_strcat(buf, "\n"); + } + return buf; +} + +static char *configfile_exists(void) +{ + static char *config_file; + struct stat statbuf; + + + if (!config_file) { + char *home = para_homedir(); + config_file = make_message("%s/.paraslash/audioc.conf", home); + free(home); + } + if (!stat(config_file, &statbuf)) + return config_file; + return NULL; +} + +int main(int argc, char *argv[]) +{ + struct sockaddr_un unix_addr; + int ret = -E_SYNTAX, fd, loaded = 0; + char *cf, *socket_name, *randname = para_tmpname(), *tmpsocket_name = NULL, + *buf = NULL, *hn = para_hostname(), *args, *home = para_homedir(); + + + if (cmdline_parser(argc, argv, &conf)) + goto out; + cf = configfile_exists(); + if (cf) { + if (cmdline_parser_configfile(cf, &conf, 0, 0, 0)) { + fprintf(stderr, "parse error in config file\n"); + exit(EXIT_FAILURE); + } + } + args = conf.inputs_num? + concat_args(conf.inputs_num, conf.inputs) : + para_strdup("stat"); + buf = para_malloc(conf.bufsize_arg); + if (conf.socket_given) + socket_name = para_strdup(conf.socket_arg); + else + socket_name = make_message( + "/var/paraslash/audiod_socket.%s", hn); + if (conf.tmpdir_given) + tmpsocket_name = make_message("%s/audioc.sock.%s.%s", + conf.tmpdir_arg, hn, randname); + else + tmpsocket_name = make_message("%s/.paraslash/audioc_sock.%s.%s", + home, hn, randname); + + ret = -E_SOCKET; + fd = create_pf_socket(tmpsocket_name, &unix_addr, S_IRUSR | S_IWUSR); + unlink(tmpsocket_name); + if (fd < 0) + goto out; + ret = -E_INIT_SOCK_ADDR; + if (init_unix_addr(&unix_addr, socket_name) < 0) + goto out; + ret = - E_CONNECT; + if (connect(fd, (struct sockaddr *)&unix_addr, UNIX_PATH_MAX) < 0) + goto out; + ret = -E_CREDENTIALS; + if (send_cred_buffer(fd, args) < 0) + goto out; + for (;;) { + int max_fileno = -1, check_write = 0; + ssize_t len; + fd_set rfd, wfd; + FD_ZERO(&rfd); + FD_ZERO(&wfd); + if (loaded && loaded > 10000) + fprintf(stderr, "loaded: %d\n", loaded); + if (loaded < conf.bufsize_arg) { + FD_SET(fd, &rfd); + max_fileno = MAX(max_fileno, fd); + } + if (loaded > 0) { + FD_SET(STDOUT_FILENO, &wfd); + max_fileno = MAX(max_fileno, STDOUT_FILENO); + check_write = 1; + } + ret = -E_OVERRUN; + if (max_fileno < 0) + goto out; + ret = select(max_fileno + 1, &rfd, &wfd, NULL, NULL); + if (ret < 0) { + ret = -E_SELECT; + goto out; + } + if (loaded < conf.bufsize_arg && FD_ISSET(fd, &rfd)) { + len = recv_bin_buffer(fd, buf + loaded, + conf.bufsize_arg - loaded); + if (len <= 0) { + ret = len < 0? -E_READ : 0; + goto out; + } + loaded += len; + } + if (check_write && FD_ISSET(STDOUT_FILENO, &wfd)) { + ret = write(STDOUT_FILENO, buf, loaded); + if (ret < 0) { + ret = -E_WRITE; + goto out; + } + loaded -= ret; + } + } +out: + if (!ret && loaded && buf) + ret = write(STDOUT_FILENO, buf, loaded); + return ret < 0? -ret : EXIT_SUCCESS; +} diff --git a/audioc.ggo b/audioc.ggo new file mode 100644 index 00000000..88133388 --- /dev/null +++ b/audioc.ggo @@ -0,0 +1,6 @@ +section "general options" +option "loglevel" l "set loglevel (0-6)" int typestr="level" default="4" no +option "socket" s "well-known socket (default=/var/paraslash/audiod.socket.$HOSTNAME)" string typestr="filename" no +option "tmpdir" d "directory for temporary socket (default=~/.paraslash)" string typestr="dirname" no +option "timeout" t "maximum time in seconds to wait for server response before giving up" int typestr="seconds" default="1" no +option "bufsize" b "size of internal buffer" int typestr="bytes" default="8192" no diff --git a/audiod.c b/audiod.c new file mode 100644 index 00000000..552577d5 --- /dev/null +++ b/audiod.c @@ -0,0 +1,1617 @@ +/* + * Copyright (C) 2005-2006 Andre Noll + * + * 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 /* gettimeofday */ +#include "para.h" + +#include "audiod.cmdline.h" +#include "list.h" +#include "close_on_fork.h" +#include "recv.h" +#include "filter.h" +#include "grab_client.cmdline.h" +#include "grab_client.h" +#include "ringbuffer.h" + +#include "error.h" +#include "audiod.h" +#include "net.h" +#include "daemon.h" +#include "string.h" + +/** define the array of error lists needed by para_audiod */ +INIT_AUDIOD_ERRLISTS; +/** define the array containing all supported audio formats */ +DEFINE_AUDIO_FORMAT_ARRAY; + +/** + * the possible modes of operation + * + * - off: disconnect from para_server + * - on: receive status information from para_server and play the audio stream + * - sb: only receive status information but not the audio stream +*/ +enum {AUDIOD_OFF, AUDIOD_ON, AUDIOD_STANDBY}; + +/** defines how to handle one supported audio format */ +struct audio_format_info { +/** pointer to the receiver for this audio format */ + struct receiver *receiver; +/** the receiver configuration */ + void *receiver_conf; +/** the number of filters that should be activated for this audio format */ + unsigned int num_filters; +/** pointer to the array of filters to be activated */ + struct filter **filters; +/** pointer to the array of filter configurations */ + void **filter_conf; +/** output of the last filter is written to stdin of this command */ + char *write_cmd; +/** do not start receiver/filters/writer before this time */ + struct timeval restart_barrier; +}; + +/** + * describes one instance of a receiver-filter-writer chain + * + * \sa receier_node, receiver, filter, filter_node, filter_chain_info + */ +struct slot_info { +/** number of the audio format in this slot */ + int format; +/** the file descriptor of the writer */ + int write_fd; +/** the process id of the writer */ + pid_t wpid; +/** time of the last successful read from the receiver */ + struct timeval rtime; +/** time the last write to the write fd happend */ + struct timeval wtime; +/** writer start time */ + struct timeval wstime; +/** did we include \a write_fd in the fdset */ + int wcheck; +/** set to one if we have sent the TERM signal to \a wpid */ + int wkilled; +/** the receiver info associated with this slot */ + struct receiver_node *receiver_node; +/** the active filter chain */ + struct filter_chain_info *fci; +}; + +static struct slot_info slot[MAX_STREAM_SLOTS]; + +/** defines one command of para_audiod */ +struct audiod_command { +/** the name of the command */ +const char *name; +/** pointer to the function that handles the command */ +int (*handler)(int, int, char**); +/** one-line description of the command */ +const char *description; +/** summary of the command line options */ +const char *synopsis; +/** the long help text */ +const char *help; +}; + +extern const char *status_item_list[NUM_STAT_ITEMS]; + +static int com_grab(int, int, char **); +static int com_cycle(int, int, char **); +static int com_help(int, int, char **); +static int com_off(int, int, char **); +static int com_on(int, int, char **); +static int com_sb(int, int, char **); +static int com_stat(int, int, char **); +static int com_term(int, int, char **); +static int stat_pipe = -1, signal_pipe; + +static struct gengetopt_args_info conf; +static struct timeval server_stream_start, sa_time_diff; +static int playing, current_decoder = -1, + audiod_status = AUDIOD_ON, offset_seconds, length_seconds, + sa_time_diff_sign = 1, audiod_socket = -1; +static char *af_status, /* the audio format announced in server status */ + *socket_name, *hostname; +/** how many status items to remember */ +#define RINGBUFFER_SIZE 32 +static void *stat_item_ringbuf; +static FILE *logfile; +static const struct timeval restart_delay = {0, 300 * 1000}; + +static struct audio_format_info afi[] = { + +[AUDIO_FORMAT_MP3] = + { + .write_cmd = "para_play", + }, +[AUDIO_FORMAT_OGG] = + { + .write_cmd = "para_play", + }, +}; + +static struct audiod_command cmds[] = { +{ +.name = "cycle", +.handler = com_cycle, +.description = "switch to next mode", +.synopsis = "cycle", +.help = + +"on -> standby -> off -> on\n" + +}, +{ +.name = "grab", +.handler = com_grab, +.description = "grab the audio stream", +.synopsis = "-- grab [grab_options]", +.help = +"grab ('splice') the audio stream at any position in the filter \n" +"chain and send that data back to the client. \n" +"Available options:\n\n" +GRAB_HELP_TXT +}, +{ +.name = "help", +.handler = com_help, +.description = "display command list or help for given command", +.synopsis = "help [command]", +.help = + +"When I was younger, so much younger than today, I never needed\n" +"anybody's help in any way. But now these days are gone, I'm not so\n" +"self assured. Now I find I've changed my mind and opened up the doors.\n" +"\n" +" -- Beatles: Help\n" + +}, +{ +.name = "off", +.handler = com_off, +.description = "deactivate para_audiod", +.synopsis = "off", +.help = + +"Close connection to para_server and stop all decoders.\n" + +}, +{ +.name = "on", +.handler = com_on, +.description = "activate para_audiod", +.synopsis = "on", +.help = + +"Establish connection to para_server, retrieve para_server's current\n" +"status. If playing, start corresponding decoder. Otherwise stop\n" +"all decoders.\n" + +}, +{ +.name = "sb", +.handler = com_sb, +.description = "enter standby mode", +.synopsis = "sb", +.help = + +"Stop all decoders but leave connection to para_server open.\n" + +}, +{ +.name = "stat", +.handler = com_stat, +.description = "print status information", +.synopsis = "stat", +.help = + +"Add para_audiod status information to para_server's status information\n" +"and dump everything to stdout.\n" + +}, +{ +.name = "term", +.handler = com_term, +.description = "terminate audiod", +.synopsis = "term", +.help = + +"Stop all decoders, shut down connection to para_server and exit.\n" + +}, +{ +.name = NULL, +} +}; + +/** iterate over all slots */ +#define FOR_EACH_SLOT(slot) for (slot = 0; slot < MAX_STREAM_SLOTS; slot++) +/** iterate over all supported audio formats */ +#define FOR_EACH_AUDIO_FORMAT(af) for (af = 0; af < NUM_AUDIO_FORMATS; af++) +/** iterate over the array of all audiod commands */ +#define FOR_EACH_COMMAND(c) for (c = 0; cmds[c].name; c++) + +/** + * get the audio format number + * \param name the name of the audio format + * + * \return The audio format number on success, -E_UNSUPPORTED_AUDIO_FORMAT if + * \a name is not a supported audio format. + */ +int get_audio_format_num(char *name) +{ + int i; + FOR_EACH_AUDIO_FORMAT(i) + if (!strcmp(name, audio_formats[i])) + return i; + return -E_UNSUPPORTED_AUDIO_FORMAT; +} + +/* + * log function. first argument is loglevel. + */ +void para_log(int ll, char* fmt,...) +{ + va_list argp; + FILE *outfd; + struct tm *tm; + time_t t1; + char str[MAXLINE] = ""; + + if (ll < conf.loglevel_arg) + return; + if (!logfile && conf.logfile_given) + logfile = open_log(conf.logfile_arg); + if (!logfile && conf.daemon_given) + return; + if (!logfile) { + if (ll < WARNING) + outfd = stdout; + else + outfd = stderr; + } else + outfd = logfile; + time(&t1); + tm = localtime(&t1); + strftime(str, MAXLINE, "%b %d %H:%M:%S", tm); + fprintf(outfd, "%s %s ", str, hostname); + if (conf.loglevel_arg <= INFO) + fprintf(outfd, "%i ", ll); + va_start(argp, fmt); + vfprintf(outfd, fmt, argp); + va_end(argp); +} + +static int client_write(int fd, const char *buf) +{ + size_t len = strlen(buf); + return write(fd, buf, len) != len? -E_CLIENT_WRITE: 1; +} + +static char *get_time_string(struct timeval *newest_stime) +{ + struct timeval now, diff, adj_stream_start, tmp; + int total = 0, use_server_time = 1; + + if (!playing) + return make_message("%s:", length_seconds? + "" : status_item_list[SI_PLAY_TIME]); + if (audiod_status == AUDIOD_OFF) + goto out; + if (sa_time_diff_sign > 0) + tv_diff(&server_stream_start, &sa_time_diff, + &adj_stream_start); + else + tv_add(&server_stream_start, &sa_time_diff, + &adj_stream_start); + tmp = adj_stream_start; + if (newest_stime && audiod_status == AUDIOD_ON) { + tv_diff(newest_stime, &adj_stream_start, &diff); + if (tv2ms(&diff) < 5000) { + tmp = *newest_stime; + use_server_time = 0; + } + } + gettimeofday(&now, NULL); + tv_diff(&now, &tmp, &diff); + total = diff.tv_sec + offset_seconds; + if (total > length_seconds) + total = length_seconds; + if (total < 0) + total = 0; +out: + return make_message( + "%s:%s%d:%02d [%d:%02d] (%d%%/%d:%02d)", + status_item_list[SI_PLAY_TIME], + use_server_time? "~" : "", + total / 60, + total % 60, + (length_seconds - total) / 60, + (length_seconds - total) % 60, + length_seconds? (total * 100 + length_seconds / 2) / + length_seconds : 0, + length_seconds / 60, + length_seconds % 60 + ); +} + +static char *audiod_status_string(void) +{ + int i; + struct timeval *newest_stime = NULL; + char *ret, *time_string, *uptime_string, *decoder_flags = + para_malloc((MAX_STREAM_SLOTS + 1) * sizeof(char)); + + FOR_EACH_SLOT(i) { + struct slot_info *s = &slot[i]; + char flag = '0'; + if (s->receiver_node) + flag += 1; + if (s->wpid > 0) + flag += 2; + if (flag != '0') + flag += s->format * 4; + decoder_flags[i] = flag; + if (s->wpid <= 0) + continue; + if (newest_stime && tv_diff(&s->wstime, newest_stime, NULL) <= 0) + continue; + newest_stime = &s->wstime; + } + decoder_flags[MAX_STREAM_SLOTS] = '\0'; + time_string = get_time_string(newest_stime); + uptime_string = uptime_str(); + ret = make_message("%s:%s\n%s:%s\n%s:%s\n%s", + status_item_list[SI_AUDIOD_UPTIME], uptime_string, + status_item_list[SI_DECODER_FLAGS], decoder_flags, + status_item_list[SI_AUDIOD_STATUS], audiod_status == AUDIOD_ON? + "on" : (audiod_status == AUDIOD_OFF? "off": "sb"), + time_string); + free(uptime_string); + free(decoder_flags); + free(time_string); + return ret; +} + +static char *configfile_exists(void) +{ + static char *config_file; + + if (!config_file) { + char *home = para_homedir(); + config_file = make_message("%s/.paraslash/audiod.conf", home); + free(home); + } + return file_exists(config_file)? config_file : NULL; +} + +static void setup_signal_handling(void) +{ + signal_pipe = para_signal_init(); + PARA_INFO_LOG("signal pipe: fd %d\n", signal_pipe); + para_install_sighandler(SIGINT); + para_install_sighandler(SIGTERM); + para_install_sighandler(SIGCHLD); + para_install_sighandler(SIGHUP); + signal(SIGPIPE, SIG_IGN); +} + +static void audiod_status_dump(void) +{ + static char *prev_status; + char *tmp = audiod_status_string(); + + if (!prev_status || strcmp(tmp, prev_status)) + stat_client_write(tmp); + free(prev_status); + prev_status = tmp; +} + +static void clear_slot(int slot_num) +{ + struct slot_info *s = &slot[slot_num]; + + PARA_INFO_LOG("clearing slot %d\n", slot_num); + memset(s, 0, sizeof(struct slot_info)); + s->format = -1; +} + +static void kill_stream_writer(int slot_num) +{ + struct slot_info *s = &slot[slot_num]; + + if (s->format < 0 || s->wkilled || s->wpid <= 0) + return; + PARA_DEBUG_LOG("kill -TERM %d (%s stream writer in slot %d)\n", + s->wpid, audio_formats[s->format], slot_num); + kill(s->wpid, SIGTERM); + s->wkilled = 1; + s->fci->error = 1; +} + +static void close_receiver(int slot_num) +{ + struct slot_info *s = &slot[slot_num]; + struct audio_format_info *a; + struct timeval now; + + if (s->format < 0 || !s->receiver_node) + return; + a = &afi[s->format]; + PARA_NOTICE_LOG("closing %s recevier in slot %d\n", + audio_formats[s->format] , slot_num); + a->receiver->close(s->receiver_node); + free(s->receiver_node); + s->receiver_node = NULL; + gettimeofday(&now, NULL); + tv_add(&now, &restart_delay, &a->restart_barrier); /* FIXME: Use set_restart_barrier() */ +} + +static void kill_all_decoders(void) +{ + int i; + + FOR_EACH_SLOT(i) + if (slot[i].format >= 0) { + PARA_INFO_LOG("stopping decoder in slot %d\n", i); + kill_stream_writer(i); + } +} + +static void set_restart_barrier(int format, struct timeval *now) +{ + struct timeval tmp; + + if (now) + tmp = *now; + else + gettimeofday(&tmp, NULL); + tv_add(&tmp, &restart_delay, &afi[format].restart_barrier); +} + +static void check_sigchld(void) +{ + pid_t pid; + int i; + struct timeval now; + gettimeofday(&now, NULL); + +reap_next_child: + pid = para_reap_child(); + if (pid <= 0) + return; + FOR_EACH_SLOT(i) { + struct slot_info *s = &slot[i]; + long lifetime; + if (s->format < 0) + continue; + if (pid == s->wpid) { + s->wpid = -1; + lifetime = now.tv_sec - s->wstime.tv_sec; + PARA_INFO_LOG("%s stream writer in slot %d died " + "after %li secs\n", + audio_formats[s->format], i, lifetime); + set_restart_barrier(s->format, &now); + goto reap_next_child; + } + } + PARA_CRIT_LOG("para_client died (pid %d)\n", pid); + goto reap_next_child; +} + +static int get_empty_slot(void) +{ + int i; + struct slot_info *s; + + FOR_EACH_SLOT(i) { + s = &slot[i]; + if (s->format < 0) { + clear_slot(i); + return i; + } + if (s->write_fd > 0 || s->wpid > 0) + continue; + if (s->receiver_node) + continue; + if (s->fci) + continue; + clear_slot(i); + return i; + } + return -E_NO_MORE_SLOTS; +} + +static int decoder_running(int format) +{ + int i, ret = 0; + struct slot_info *s; + + FOR_EACH_SLOT(i) { + s = &slot[i]; + if (s->format == format && s->receiver_node) + ret |= 1; + if (s->format == format && s->wpid > 0) + ret |= 2; + } + return ret; +} + +static void close_stat_pipe(void) +{ + char *msg; + int i; + + if (stat_pipe < 0) + return; + PARA_NOTICE_LOG("%s", "closing status pipe\n"); + close(stat_pipe); + del_close_on_fork_list(stat_pipe); + stat_pipe = -1; + kill_all_decoders(); + for (i = 0; i < RINGBUFFER_SIZE; i++) + ringbuffer_add(stat_item_ringbuf, para_strdup(NULL)); + dump_empty_status(); + length_seconds = 0; + offset_seconds = 0; + audiod_status_dump(); + playing = 0; + msg = make_message("%s:no connection to para_server\n", + status_item_list[SI_STATUS_BAR]); + ringbuffer_add(stat_item_ringbuf, msg); + stat_client_write(msg); + free(msg); +} + +static void __noreturn clean_exit(int status, const char *msg) +{ + PARA_EMERG_LOG("%s\n", msg); + kill_all_decoders(); + if (socket_name) + unlink(socket_name); + if (stat_pipe >= 0) + close_stat_pipe(); + exit(status); +} + +static char *glob_cmd(char *cmd) +{ + char *ret, *replacement; + struct timeval tmp, delay, rss; /* real stream start */ + + delay.tv_sec = conf.stream_delay_arg / 1000; + delay.tv_usec = (conf.stream_delay_arg % 1000) * 1000; +// PARA_INFO_LOG("delay: %lu:%lu\n", delay.tv_sec, delay.tv_usec); + if (sa_time_diff_sign < 0) + tv_add(&server_stream_start, &sa_time_diff, &rss); + else + tv_diff(&server_stream_start, &sa_time_diff, &rss); + tv_add(&rss, &delay, &tmp); + replacement = make_message("%lu:%lu", tmp.tv_sec, tmp.tv_usec); + ret = s_a_r(cmd, "STREAM_START", replacement); + free(replacement); + if (!ret) + goto out; + PARA_INFO_LOG("cmd: %s, repl: %s\n", cmd, ret); + { + struct timeval now; + gettimeofday(&now, NULL); + PARA_INFO_LOG("now: %lu:%lu\n", now.tv_sec, now.tv_usec); + } +out: + return ret; +} + +/** get the number of filters for the given audio format */ +int num_filters(int audio_format_num) +{ + return afi[audio_format_num].num_filters; +} + +static void open_filters(int slot_num) +{ + struct slot_info *s = &slot[slot_num]; + struct audio_format_info *a = &afi[s->format]; + int nf = a->num_filters; + int i; + + s->fci = para_calloc(sizeof(struct filter_chain_info)); + INIT_LIST_HEAD(&s->fci->filters); + if (!nf) + return; + s->fci->inbuf = s->receiver_node->buf; + s->fci->in_loaded = &s->receiver_node->loaded; + s->fci->outbuf = s->receiver_node->buf; + s->fci->out_loaded = &s->receiver_node->loaded; + s->fci->eof = &s->receiver_node->eof; + for (i = 0; i < nf; i++) { + struct filter_node *fn = para_calloc(sizeof(struct filter_node)); + fn->conf = a->filter_conf[i]; + fn->fci = s->fci; + fn->filter = a->filters[i]; + INIT_LIST_HEAD(&fn->callbacks); + list_add_tail(&fn->node, &s->fci->filters); + fn->filter->open(fn); + PARA_NOTICE_LOG("%s filter %d/%d (%s) started in slot %d\n", + audio_formats[s->format], i + 1, nf, + fn->filter->name, slot_num); + s->fci->outbuf = fn->buf; + s->fci->out_loaded = &fn->loaded; + } + PARA_DEBUG_LOG("output buffer for filter chain %p: %p\n", s->fci, + s->fci->outbuf); +} + +static struct filter_node *find_filter_node(int slot_num, int format, int filternum) +{ + struct filter_node *fn; + int i, j; + + FOR_EACH_SLOT(i) { + struct slot_info *s = &slot[i]; + if (s->format < 0 || !s->fci) + continue; + if (slot_num >= 0 && slot_num != i) + continue; + if (format >= 0 && s->format != format) + continue; + if (num_filters(i) < filternum) + continue; + /* success */ + j = 1; + list_for_each_entry(fn, &s->fci->filters, node) + if (filternum <= 0 || j++ == filternum) + break; + return fn; + } + return NULL; +} + +static void start_stream_writer(int slot_num) +{ + int ret, fds[3] = {1, -1, -1}; + struct slot_info *s = &slot[slot_num]; + struct audio_format_info *a = &afi[s->format]; + char *glob = glob_cmd(a->write_cmd); + + PARA_INFO_LOG("starting stream writer: %s\n", glob? glob : a->write_cmd); + open_filters(slot_num); + + ret = para_exec_cmdline_pid(&s->wpid, glob? glob : a->write_cmd, fds); + free(glob); + if (ret < 0) { + PARA_ERROR_LOG("exec failed (%d)\n", ret); + return; + } + s->write_fd = fds[0]; + add_close_on_fork_list(s->write_fd); + /* we write to this fd in do_select, so we need non-blocking */ + fcntl(s->write_fd, F_SETFL, O_NONBLOCK); + gettimeofday(&s->wstime, NULL); + current_decoder = slot_num; + activate_inactive_grab_clients(slot_num, s->format, &s->fci->filters); +} + +static void open_receiver(int format) +{ + struct audio_format_info *a = &afi[format]; + struct slot_info *s; + int ret, slot_num; + + slot_num = get_empty_slot(); + if (slot_num < 0) + clean_exit(EXIT_FAILURE, PARA_STRERROR(-slot_num)); + s = &slot[slot_num]; + s->format = format; + gettimeofday(&s->rtime, NULL); + s->wtime = s->rtime; + s->receiver_node = para_calloc(sizeof(struct receiver_node)); + s->receiver_node->conf = a->receiver_conf; + ret = a->receiver->open(s->receiver_node); + if (ret < 0) { + PARA_ERROR_LOG("failed to open receiver (%s)\n", + PARA_STRERROR(-ret)); + free(s->receiver_node); + s->receiver_node = NULL; + return; + } + PARA_NOTICE_LOG("started %s: %s receiver in slot %d\n", + audio_formats[s->format], a->receiver->name, slot_num); +} + +static int is_frozen(int format) +{ + struct timeval now; + struct audio_format_info *a = &afi[format]; + + gettimeofday(&now, NULL); + return (tv_diff(&now, &a->restart_barrier, NULL) > 0)? 0 : 1; +} + +static void start_current_receiver(void) +{ + int i; + + if (!af_status) + return; + i = get_audio_format_num(af_status); + if (i < 0) + return; + if ((decoder_running(i) & 1) || is_frozen(i)) + return; + open_receiver(i); +} + +static void compute_time_diff(const struct timeval *status_time) +{ + struct timeval now, tmp, diff; + static int count; + int sign; + const struct timeval max_deviation = {0, 500 * 1000}; + const int time_smooth = 5; + + gettimeofday(&now, NULL); + sign = tv_diff(status_time, &now, &diff); +// PARA_NOTICE_LOG("%s: sign = %i, sa_time_diff_sign = %i\n", __func__, +// sign, sa_time_diff_sign); + if (!count) { + sa_time_diff_sign = sign; + sa_time_diff = diff; + count++; + return; + } + if (count > 5) { + int s = tv_diff(&diff, &sa_time_diff, &tmp); + if (tv_diff(&max_deviation, &tmp, NULL) < 0) + PARA_WARNING_LOG("time diff jump: %lims\n", + s * tv2ms(&tmp)); + } + count++; + sa_time_diff_sign = tv_convex_combination( + sa_time_diff_sign * time_smooth, &sa_time_diff, + count > 10? sign : sign * time_smooth, &diff, + &tmp); + sa_time_diff = tmp; + PARA_INFO_LOG("time diff (cur/avg): " + "%li:%lu/%li:%lu\n", + sign * diff.tv_sec, (diff.tv_usec + 500) / 1000, + sa_time_diff_sign * sa_time_diff.tv_sec, + (sa_time_diff.tv_usec + 500)/ 1000); +} + +static void check_stat_line(char *line) +{ + int itemnum; + size_t ilen = 0; + struct timeval tv; + + if (!line) + return; + ringbuffer_add(stat_item_ringbuf, line); + stat_client_write(line); + itemnum = stat_line_valid(line); + if (itemnum < 0) + return; + ilen = strlen(status_item_list[itemnum]); + switch (itemnum) { + case SI_STATUS: + playing = strstr(line, "playing")? 1 : 0; + break; + case SI_FORMAT: + free(af_status); + af_status = para_strdup(line + ilen + 1); + break; + case SI_OFFSET: + offset_seconds = atoi(line + ilen + 1); + break; + case SI_LENGTH: + length_seconds = atoi(line + ilen + 1); + break; + case SI_STREAM_START: + if (sscanf(line + ilen + 1, "%lu.%lu", + &tv.tv_sec, &tv.tv_usec) == 2) + server_stream_start = tv; + break; + case SI_CURRENT_TIME: + if (sscanf(line + ilen + 1, "%lu.%lu", &tv.tv_sec, + &tv.tv_usec) == 2) + compute_time_diff(&tv); + break; + } +} + +static void handle_signal(int sig) +{ + switch (sig) { + case SIGCHLD: + return check_sigchld(); + case SIGINT: + case SIGTERM: + case SIGHUP: + PARA_EMERG_LOG("terminating on signal %d\n", sig); + clean_exit(EXIT_FAILURE, "caught deadly signal"); + return; + } +} + +static void check_timeouts(void) +{ + struct timeval now; + int slot_num, timeout = conf.stream_timeout_arg; + + gettimeofday(&now, NULL); + FOR_EACH_SLOT(slot_num) { + struct slot_info *s = &slot[slot_num]; + if (s->format < 0) + continue; + /* check read time */ + if (s->receiver_node && + now.tv_sec > s->rtime.tv_sec + timeout) { + PARA_INFO_LOG("%s input buffer (slot %d) not ready\n", + audio_formats[s->format], slot_num); + if (s->fci) + s->fci->error = 42; + else + close_receiver(slot_num); + } + /* check write time */ + if (s->wpid > 0 && !s->wkilled && + now.tv_sec > s->wtime.tv_sec + timeout) { + PARA_INFO_LOG("%s output buffer (slot %d) not ready\n", + audio_formats[s->format], slot_num); + if (s->fci) + s->fci->error = 42; + } + } +} + +static size_t get_loaded_bytes(int slot_num) +{ + size_t loaded = 0; + struct slot_info *s = &slot[slot_num]; + struct receiver_node *rn = s->receiver_node; + + if (s->format < 0) + goto out; + + if (afi[s->format].num_filters) { + if (s->fci) + loaded = *s->fci->out_loaded; + } else { + if (rn) + loaded = rn->loaded; + } +out: + return loaded; +} + + +static void close_decoder_if_idle(int slot_num) +{ + struct slot_info *s = &slot[slot_num]; + struct receiver_node *rn = s->receiver_node; + + if (s->format < 0) + return; + if (!s->fci) + return; + if (!rn->eof && !s->fci->error && s->wpid > 0) + return; + if (!s->fci->error && s->wpid > 0) { /* eof */ + if (filter_io(s->fci) > 0) + return; + if (get_loaded_bytes(slot_num)) + return; + } + if (s->write_fd > 0) { + PARA_INFO_LOG("slot %d: closing write fd %d\n", slot_num, + s->write_fd); + close(s->write_fd); + del_close_on_fork_list(s->write_fd); + s->write_fd = -1; + } + if (s->wpid > 0) + return; /* wait until writer dies before closing filters */ + PARA_INFO_LOG("closing all filters in slot %d (filter_chain %p)\n", + slot_num, s->fci); + close_filters(s->fci); + free(s->fci); + close_receiver(slot_num); + clear_slot(slot_num); +} + +static int set_stream_fds(fd_set *wfds) +{ + int i, max_fileno = -1; + + check_timeouts(); + FOR_EACH_SLOT(i) { + struct slot_info *s = &slot[i]; + struct audio_format_info *a; + struct receiver_node *rn; + + close_decoder_if_idle(i); + s->wcheck = 0; + if (s->format < 0) + continue; + a = &afi[s->format]; + rn = s->receiver_node; + if (rn && rn->loaded && !s->wpid) { + PARA_INFO_LOG("no writer in slot %d\n", i); + start_stream_writer(i); + } + if (s->write_fd <= 0) + continue; + if (!get_loaded_bytes(i)) + continue; + FD_SET(s->write_fd, wfds); + s->wcheck = 1; + max_fileno = MAX(s->write_fd, max_fileno); + } +// PARA_INFO_LOG("return %d\n", max_fileno); + return max_fileno; +} + +static int write_audio_data(int slot_num) +{ + struct slot_info *s = &slot[slot_num]; + struct audio_format_info *a = &afi[s->format]; + struct receiver_node *rn = s->receiver_node; + int rv; + char **buf; + size_t *len; + + if (a->num_filters) { + buf = &s->fci->outbuf; + len = s->fci->out_loaded; + } else { + buf = &rn->buf; + len = &rn->loaded; + } + PARA_DEBUG_LOG("writing %p (%d bytes)\n", *buf, *len); + rv = write(s->write_fd, *buf, *len); + PARA_DEBUG_LOG("wrote %d/%d\n", rv, *len); + if (rv < 0) { + PARA_WARNING_LOG("write error in slot %d (fd %d): %s\n", + slot_num, s->write_fd, strerror(errno)); + *len = 0; + s->fci->error = E_WRITE_AUDIO_DATA; + } else if (rv != *len) { + PARA_DEBUG_LOG("partial %s write (%i/%i) for slot %d\n", + audio_formats[s->format], rv, *len, slot_num); + *len -= rv; + memmove(*buf, *buf + rv, *len); + } else + *len = 0; + if (rv > 0) + gettimeofday(&s->wtime, NULL); + return rv; +} + +static void slot_io(fd_set *wfds) +{ + int ret, i; + + FOR_EACH_SLOT(i) { + struct slot_info *s = &slot[i]; + struct receiver_node *rn = s->receiver_node; + + if (rn && rn->loaded) + gettimeofday(&s->rtime, NULL); + if (s->format >= 0 && s->write_fd > 0 && s->fci) { + ret = filter_io(s->fci); + if (ret < 0) + s->fci->error = -ret; +// PARA_DEBUG_LOG("slot %d, filter io %d bytes, check write: %d, loaded: %d/%d, eof: %d\n", +// i, ret, s->wcheck, rn->loaded, *s->fci->out_loaded, rn->eof); + } + if (s->write_fd <= 0 || !s->wcheck || !FD_ISSET(s->write_fd, wfds)) + continue; + write_audio_data(i); + } +} + +static int parse_stream_command(const char *txt, char **cmd) +{ + char *p = strchr(txt, ':'); + int i; + + if (!p) + return -E_MISSING_COLON; + p++; + FOR_EACH_AUDIO_FORMAT(i) { + if (strncmp(txt, audio_formats[i], strlen(audio_formats[i]))) + continue; + *cmd = p; + return i; + } + return -E_UNSUPPORTED_AUDIO_FORMAT; +} + +static int add_filter(int format, char *cmdline) +{ + struct audio_format_info *a = &afi[format]; + int filter_num, nf = a->num_filters; + + filter_num = check_filter_arg(cmdline, &a->filter_conf[nf]); + if (filter_num < 0) + return filter_num; + a->filters[nf] = &filters[filter_num]; + a->num_filters++; + PARA_INFO_LOG("%s filter %d: %s\n", audio_formats[format], nf + 1, + a->filters[nf]->name); + return filter_num; +} + +static int setup_default_filters(void) +{ + int i, ret = 1; + + FOR_EACH_AUDIO_FORMAT(i) { + struct audio_format_info *a = &afi[i]; + char *tmp; + int j; + if (a->num_filters) + continue; + /* add "dec" to audio format name */ + tmp = make_message("%sdec", audio_formats[i]); + for (j = 0; filters[j].name; j++) + if (!strcmp(tmp, filters[j].name)) + break; + free(tmp); + ret = -E_UNSUPPORTED_FILTER; + if (!filters[j].name) + goto out; + tmp = para_strdup(filters[j].name); + ret = add_filter(i, tmp); + free(tmp); + if (ret < 0) + goto out; + PARA_INFO_LOG("%s -> default filter: %s\n", audio_formats[i], filters[j].name); + ret = add_filter(i, "wav"); + if (ret < 0) + goto out; + PARA_INFO_LOG("%s -> default filter: wav\n", audio_formats[i]); + } +out: + return ret; +} + +static int init_stream_io(void) +{ + int i, ret, receiver_num; + char *cmd; + + for (i = 0; i < conf.stream_write_cmd_given; i++) { + ret = parse_stream_command(conf.stream_write_cmd_arg[i], &cmd); + if (ret < 0) + goto out; + afi[ret].write_cmd = para_strdup(cmd); + PARA_INFO_LOG("%s write command: %s\n", audio_formats[ret], afi[ret].write_cmd); + } + for (i = 0; receivers[i].name; i++) { + PARA_INFO_LOG("initializing %s receiver\n", receivers[i].name); + receivers[i].init(&receivers[i]); + } + for (i = 0; i < conf.receiver_given; i++) { + char *arg = conf.receiver_arg[i]; + char *recv = strchr(arg, ':'); + ret = -E_MISSING_COLON; + if (!recv) + goto out; + *recv = '\0'; + recv++; + ret = get_audio_format_num(arg); + if (ret < 0) + goto out; + afi[ret].receiver_conf = check_receiver_arg(recv, &receiver_num); + if (!afi[ret].receiver_conf) { + ret = -E_RECV_SYNTAX; + goto out; + } + afi[ret].receiver = &receivers[receiver_num]; + } + /* use the first available receiver with no arguments + * for those audio formats for which no receiver + * was specified + */ + cmd = para_strdup(receivers[0].name); + FOR_EACH_AUDIO_FORMAT(i) { + struct audio_format_info *a = &afi[i]; + if (a->receiver_conf) + continue; + a->receiver_conf = check_receiver_arg(cmd, &receiver_num); + if (!a->receiver_conf) + return -E_RECV_SYNTAX; + a->receiver = &receivers[receiver_num]; + } + free(cmd); + /* filters */ + filter_init(filters); + FOR_EACH_AUDIO_FORMAT(i) { + afi[i].filter_conf = para_malloc((conf.filter_given + 1) * sizeof(char *)); + afi[i].filters = para_malloc((conf.filter_given + 1) * sizeof(struct filter *)); + } + if (!conf.no_default_filters_given) + return setup_default_filters(); + for (i = 0; i < conf.filter_given; i++) { + char *arg = conf.filter_arg[i]; + char *filter_name = strchr(arg, ':'); + ret = -E_MISSING_COLON; + if (!filter_name) + goto out; + *filter_name = '\0'; + filter_name++; + ret = get_audio_format_num(arg); + if (ret < 0) + goto out; + ret = add_filter(ret, filter_name); + if (ret < 0) + goto out; + } + ret = 1; +out: + return ret; +} + +static int dump_commands(int fd) +{ + char *buf = para_strdup(""), *tmp = NULL; + int i; + ssize_t ret; + + FOR_EACH_COMMAND(i) { + tmp = make_message("%s%s\t%s\n", buf, cmds[i].name, + cmds[i].description); + free(buf); + buf = tmp; + } + ret = client_write(fd, buf); + free(buf); + return ret; +} + +/* + * command handlers don't close their fd on errors (ret < 0) so that + * its caller can send an error message. Otherwise (ret >= 0) it's up + * to each individual command to close the fd if necessary. + */ + +static int com_help(int fd, int argc, char **argv) +{ + int i, ret; + char *buf; + const char *dflt = "No such command. Available commands:\n"; + + if (argc < 2) { + ret = dump_commands(fd); + goto out; + } + FOR_EACH_COMMAND(i) { + if (strcmp(cmds[i].name, argv[1])) + continue; + buf = make_message( + "NAME\n\t%s -- %s\n" + "SYNOPSIS\n\tpara_audioc %s\n" + "DESCRIPTION\n%s\n", + argv[1], + cmds[i].description, + cmds[i].synopsis, + cmds[i].help + ); + ret = client_write(fd, buf); + free(buf); + goto out; + } + ret = client_write(fd, dflt); + if (ret > 0) + ret = dump_commands(fd); +out: + if (ret >= 0) + close(fd); + return ret; +} + +static int com_stat(int fd, __unused int argc, __unused char **argv) +{ + int i, ret; + char *buf = audiod_status_string(); + + buf = para_strcat(buf, "\n"); + for (i = RINGBUFFER_SIZE - 1; i >= 0; i--) { + char *tmp, *line = ringbuffer_get(stat_item_ringbuf, i); + if (!line) + continue; + tmp = make_message("%s\n", line); + buf = para_strcat(buf, tmp); + free(tmp); + } + ret = client_write(fd, buf); + if (ret > 0) + ret = stat_client_add(fd); + free(buf); + return ret; +} + +#if 0 +static char *list_filters(void) +{ + int i, j; + char *tmp, *msg = make_message("format\tnum\tcmd\n"); + + FOR_EACH_AUDIO_FORMAT(i) { + for (j = 0; j < afi[i].num_filters; j++) { + tmp = make_message("%s\t%i\t%s\n", + afi[i].name, j, afi[i].filter_cmds[j]); + msg = para_strcat(msg, tmp); + free(tmp); + } + tmp = make_message("%s\t%i\t%s\n", afi[i].name, + j, afi[i].write_cmd); + msg = para_strcat(msg, tmp); + free(tmp); + } + return msg; +} +#endif + +static int com_grab(int fd, int argc, char **argv) +{ + struct grab_client *gc; + struct filter_node *fn; + int err; + + PARA_INFO_LOG("argc: %d, argv[0]: %s, optind: %d\n", argc, argv[0], optind); + gc = grab_client_new(fd, argc, argv, &err); + PARA_INFO_LOG("argc: %d, argv[0]: %s, optind: %d\n", argc, argv[0], optind); + if (!gc) + goto err_out; + fn = find_filter_node(gc->conf->slot_arg, gc->audio_format_num, gc->conf->filter_num_arg); + if (fn) + activate_grab_client(gc, fn); + return 1; +err_out: + if (err != -E_GC_HELP_GIVEN) + return err; + err = client_write(fd, "Usage: para_audioc [audioc_options] -- " + "grab [grab_options]\nAvailable options:\n"); + if (err < 0) + return err; + err = client_write(fd, GRAB_HELP_TXT); + if (err < 0) + return err; + close(fd); + return 1; +} + +static int __noreturn com_term(int fd, __unused int argc, __unused char **argv) +{ + close(fd); + clean_exit(EXIT_SUCCESS, "terminating on user request"); +} + +static int com_on(int fd, __unused int argc, __unused char **argv) +{ + audiod_status = AUDIOD_ON; + close(fd); + return 1; +} + +static int com_off(int fd, __unused int argc, __unused char **argv) +{ + audiod_status = AUDIOD_OFF; + close(fd); + return 1; +} + +static int com_sb(int fd, __unused int argc, __unused char **argv) +{ + audiod_status = AUDIOD_STANDBY; + close(fd); + return 1; +} + +static int com_cycle(int fd, int argc, char **argv) +{ + switch (audiod_status) { + case AUDIOD_ON: + return com_sb(fd, argc, argv); + break; + case AUDIOD_OFF: + return com_on(fd, argc, argv); + break; + case AUDIOD_STANDBY: + return com_off(fd, argc, argv); + break; + } + close(fd); + return 1; +} + +static int check_perms(struct ucred *c) +{ + int i; + + if (!conf.user_allow_given) + return 1; + for (i = 0; i < conf.user_allow_given; i++) + if (c->uid == conf.user_allow_arg[i]) + return 1; + return -E_UCRED_PERM; +} + +static int handle_connect(void) +{ + int i, argc, ret, clifd = -1; + struct ucred c; + char *buf = para_malloc(MAXLINE), **argv = NULL; + struct sockaddr_un unix_addr; + + ret = para_accept(audiod_socket, &unix_addr, sizeof(struct sockaddr_un)); + if (ret < 0) + goto out; + clifd = ret; + ret = recv_cred_buffer(clifd, buf, MAXLINE - 1, &c); + if (ret < 0) + goto out; + PARA_INFO_LOG("pid: %i, uid: %i, gid: %i, ret: %i, buf: %s\n", c.pid, c.uid, c.gid, ret, buf); + buf[ret] = '\0'; + ret = check_perms(&c); + if (ret < 0) + goto out; + argc = split_args(buf, &argv, '\n'); + PARA_INFO_LOG("argv[0]: %s\n", argv[0]); + for (i = 0; cmds[i].name; i++) { + if (strcmp(cmds[i].name, argv[0])) + continue; + ret = cmds[i].handler(clifd, argc + 1, argv); + goto out; + } + ret = -E_INVALID_AUDIOD_CMD; /* cmd not found */ +out: + free(buf); + free(argv); + if (clifd > 0 && ret < 0 && ret != -E_CLIENT_WRITE) { + char *tmp = make_message("%s\n", PARA_STRERROR(-ret)); + client_write(clifd, tmp); + free(tmp); + close(clifd); + } + return ret; +} + +static void audiod_get_socket(void) +{ + struct sockaddr_un unix_addr; + + if (conf.socket_given) + socket_name = para_strdup(conf.socket_arg); + else { + char *hn = para_hostname(); + socket_name = make_message("/var/paraslash/audiod_socket.%s", + hn); + free(hn); + } + PARA_NOTICE_LOG("connecting to local socket %s\n", socket_name); + if (conf.force_given) + unlink(socket_name); + audiod_socket = create_pf_socket(socket_name, &unix_addr, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IWOTH); + if (audiod_socket < 0) { + PARA_EMERG_LOG("%s", "can not connect to socket\n"); + exit(EXIT_FAILURE); /* do not unlink socket */ + } + if (listen(audiod_socket, 5) < 0) { + PARA_EMERG_LOG("%s", "can not listen on socket\n"); + exit(EXIT_FAILURE); /* do not unlink socket */ + } + add_close_on_fork_list(audiod_socket); +} + +static int open_stat_pipe(void) +{ + int ret, fd[3] = {-1, 1, 0}; + char *argv[] = {BINDIR "/para_client", "stat", NULL}; + pid_t pid; + ret = para_exec(&pid, BINDIR "/para_client", argv, fd); + if (ret >= 0) { + ret = fd[1]; + PARA_NOTICE_LOG("stat pipe opened, fd %d\n", ret); + add_close_on_fork_list(ret); + } else + clean_exit(EXIT_FAILURE, "failed to open status pipe"); + return ret; +} + +static int pre_select(fd_set *rfds, fd_set *wfds, struct timeval *tv) +{ + int i, ret, max = -1; + + FOR_EACH_SLOT(i) { + struct slot_info *s = &slot[i]; + struct audio_format_info *a; + struct receiver_node *rn = s->receiver_node; + if (s->format < 0 || !rn) + continue; + a = &afi[s->format]; + ret = a->receiver->pre_select(rn, rfds, wfds, tv); +// PARA_NOTICE_LOG("%s preselect: %d\n", a->receiver->name, ret); + max = MAX(max, ret); + } + return max; + +} +static void audiod_post_select(int select_ret, fd_set *rfds, fd_set *wfds) +{ + int i, ret; + + FOR_EACH_SLOT(i) { + struct slot_info *s = &slot[i]; + struct audio_format_info *a; + struct receiver_node *rn = s->receiver_node; + if (s->format < 0 || !rn || rn->eof) + continue; + a = &afi[s->format]; + ret = a->receiver->post_select(rn, select_ret, rfds, wfds); + if (ret <= 0) { + if (ret) + PARA_ERROR_LOG("%s post select failed: %s (slot %d)\n", + a->receiver->name, PARA_STRERROR(-ret), i); + else + PARA_INFO_LOG("eof in slot %d\n", i); + rn->eof = 1; + } + if (ret < 0 && s->fci) + s->fci->error = ret; + } +} + +/* TODO: move everything before the select call to pre_select() */ +static void __noreturn audiod_mainloop(void) +{ + fd_set rfds, wfds; + int ret, max_fileno, sbo = 0; + char status_buf[STRINGSIZE] = ""; + struct timeval tv; +repeat: + FD_ZERO(&rfds); + FD_ZERO(&wfds); + max_fileno = 0; + if (audiod_status != AUDIOD_ON) + kill_all_decoders(); + else if (playing) + start_current_receiver(); + max_fileno = set_stream_fds(&wfds); + /* stat pipe (read) */ + if (stat_pipe >= 0 && audiod_status == AUDIOD_OFF) + close_stat_pipe(); + if (stat_pipe < 0 && audiod_status != AUDIOD_OFF) { + stat_pipe = open_stat_pipe(); + sbo = 0; + status_buf[0] = '\0'; + } + if (stat_pipe >= 0 && audiod_status != AUDIOD_OFF) { + FD_SET(stat_pipe, &rfds); + max_fileno = MAX(max_fileno, stat_pipe); + } + /* always check signal pipe */ + FD_SET(signal_pipe, &rfds); + max_fileno = MAX(max_fileno, signal_pipe); + /* local socket */ + if (audiod_socket < 0) + audiod_get_socket(); /* doesn't return on errors */ + FD_SET(audiod_socket, &rfds); + max_fileno = MAX(max_fileno, audiod_socket); + tv.tv_sec = 0; + tv.tv_usec = 200 * 1000; + ret = pre_select(&rfds, &wfds, &tv); + max_fileno = MAX(max_fileno, ret); + ret = select(max_fileno + 1, &rfds, &wfds, NULL, &tv); + if (ret < 0 && errno != EINTR) + PARA_ERROR_LOG("select returned %d (%s)\n", ret, + strerror(errno)); + if (audiod_status != AUDIOD_OFF) + audiod_status_dump(); + if (ret < 0) + goto repeat; + audiod_post_select(ret, &rfds, &wfds); + /* read status pipe */ + if (stat_pipe >=0 && FD_ISSET(stat_pipe, &rfds)) { + ret = read(stat_pipe, status_buf + sbo, STRINGSIZE - 1 - sbo); + if (ret <= 0) { + close_stat_pipe(); + /* avoid busy loop if server is down */ + while (sleep(1) > 0) + ; /* try again*/ + } else { + status_buf[ret + sbo] = '\0'; + sbo = for_each_line(status_buf, ret + sbo, + &check_stat_line, 0); + } + } + slot_io(&wfds); + if (FD_ISSET(audiod_socket, &rfds)) { + ret = handle_connect(); + if (ret < 0) { + PARA_ERROR_LOG("%s\n", PARA_STRERROR(-ret)); + } + } + /* signals */ + if (FD_ISSET(signal_pipe, &rfds)) { + int sig_nr = para_next_signal(); + if (sig_nr > 0) + handle_signal(sig_nr); + } + goto repeat; +} + +static void set_initial_status(void) +{ + audiod_status = AUDIOD_ON; + if (!conf.mode_given) + return; + if (!strcmp(conf.mode_arg, "sb")) { + audiod_status = AUDIOD_STANDBY; + return; + } + if (!strcmp(conf.mode_arg, "off")) { + audiod_status = AUDIOD_OFF; + return; + } + if (strcmp(conf.mode_arg, "on")) + PARA_WARNING_LOG("%s", "invalid mode\n"); +} + +int __noreturn main(int argc, char *argv[]) +{ + char *cf; + int i; + + valid_fd_012(); + hostname = para_hostname(); + cmdline_parser(argc, argv, &conf); + para_drop_privileges(conf.user_arg); + cf = configfile_exists(); + if (cf) { + if (cmdline_parser_configfile(cf, &conf, 0, 0, 0)) { + fprintf(stderr, "parse error in config file\n"); + exit(EXIT_FAILURE); + } + } + log_welcome("para_audiod", conf.loglevel_arg); + i = init_stream_io(); + if (i < 0) { + fprintf(stderr, "init stream io error: %s\n", + PARA_STRERROR(-i)); + exit(EXIT_FAILURE); + } + server_uptime(UPTIME_SET); + set_initial_status(); + FOR_EACH_SLOT(i) + clear_slot(i); + stat_item_ringbuf = ringbuffer_new(RINGBUFFER_SIZE); + init_grabbing(); + setup_signal_handling(); + if (conf.daemon_given) + daemon_init(); + audiod_mainloop(); +} diff --git a/audiod.ggo b/audiod.ggo new file mode 100644 index 00000000..cafc8519 --- /dev/null +++ b/audiod.ggo @@ -0,0 +1,100 @@ +section "general options" +option "user" u "run as user 'name'. Read the output of 'para_server -h' for a detailed information on this option." string typestr="name" no +option "loglevel" l "set loglevel (0-6)" int typestr="level" default="4" no +option "daemon" d "run as background daemon" flag off +option "force" F "force startup even if socket exits" flag off +option "logfile" L "(default=stdout/stderr)" string typestr="filename" no +option "mode" m "mode to use on startup (on/off/sb)" string typestr="mode" default="on" no +option "socket" s "well-known socket to listen on (default=/var/paraslash/audiod_sock.)" string typestr="filename" no + +option "user_allow" - + +"allow this user to connect to para_audiod. +May be specified multiple times. If not +specified at all, allow all users to +connect." + +int typestr="uid" default="-1" no multiple + +section "stream i/o options." + +################# + +option "receiver" r "Select receiver. + +May be given multiple times, once for each +supported audio format. receiver_spec +consists of an audio format, the receiver +name and any options for that receiver, +seperated by colons. + +Example: + +-r mp3:http:-i:www.paraslash.org:-p:8009 +" + +string typestr="receiver_spec" default="http" no multiple + +################# +option "no_default_filters" D "Configure filters manually. + +If (and only if) this option is set, the +--filter options take effect. Otherwise, the +compiled-in default filters mp3dec (oggdec) +and wav are activated for mp3 (ogg) streams." + +flag off +################# + +option "filter" f "Select filter(s) manually. + +May be given multiple times. filter_spec +consists of an audio format, the name of the +filter, and any options for that filter, +separated by colons. + +Examples: + -f mp3:mp3dec + -f:mp3:compress:--anticlip:--volume:2 + +Note that these options are ignored by default, +see --no_default_filters." + +string typestr="filter_spec" no multiple + +################# + +option "stream_write_cmd" w + +"Specify stream writer. + +May be given multiple times, once for each +supported audio format. Default value is +'para_play' for both mp3 and ogg. You can use +the START_TIME() macro for these commands. +Each occurence of START_TIME() gets replaced +at runtime by the stream start time announced +by para_server, plus any offsets." + +string typestr="format:command" no multiple + +################# + +option "stream_delay" - + +"Time to add to para_server's start_time. + +Amount of time to be added to the server +stream start time for stream_write_cmd if +START_TIME() was given. Useful for +syncronizing the audio output of clients." + +int typestr="milliseconds" default="200" no + +################# + +option "stream_timeout" - + +"Deactivate slot if idle for that many seconds" + +int typestr="seconds" default="30" no diff --git a/audiod.h b/audiod.h new file mode 100644 index 00000000..e6bd9b33 --- /dev/null +++ b/audiod.h @@ -0,0 +1,12 @@ +/** \file audiod.h symbols exported from audiod.c */ +int num_filters(int audio_format_num); +int get_audio_format_num(char *name); +enum { + AUDIO_FORMAT_MP3, + AUDIO_FORMAT_OGG +}; +#define NUM_AUDIO_FORMATS (AUDIO_FORMAT_OGG + 1) +extern const char *audio_formats[]; +#define DEFINE_AUDIO_FORMAT_ARRAY const char *audio_formats[] = {"mp3", "ogg", NULL} +#define MAX_STREAM_SLOTS 5 + diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 00000000..5c950baa --- /dev/null +++ b/autogen.sh @@ -0,0 +1,12 @@ +#!/bin/sh +echo preparing... +if test -f Makefile; then + make maintainer-clean > /dev/null +fi +aclocal &> /dev/null +autoconf +autoheader +echo configuring... +./configure $@ > /dev/null +echo compiling... +make clean all > /dev/null diff --git a/bash_completion b/bash_completion new file mode 100644 index 00000000..2f074990 --- /dev/null +++ b/bash_completion @@ -0,0 +1,38 @@ +_para() +{ +local cur prev sect i manpath tmp +COMPREPLY=() +cur=${COMP_WORDS[COMP_CWORD]} +prev=${COMP_WORDS[COMP_CWORD-1]} + +#_expand || return 0 + +case "${COMP_WORDS[1]}" in + cs|cs|strdel|strq) + COMPREPLY=( $( eval para_client streams | grep "^$cur" \ + 2>/dev/null ) ) + return 0 + ;; + info|la|us|ca|ls|pic|us) + COMPREPLY=( $( eval para_client ls "$cur%" ) ) + return 0 + ;; + sa) + COMPREPLY=( $( eval para_client laa | grep "^$cur" \ + 2>/dev/null ) ) + return 0 + + + +esac +# default completion if parameter contains / +# [[ "$cur" == */* ]] && return 0 +#echo "cur=$cur" +COMPREPLY=( $( eval para_client help | cut -f 1 | sed 1d | grep ^$cur 2>/dev/null ) ) +# weed out directory path names and paths to man pages +COMPREPLY=( ${COMPREPLY[@]##*/?(:)} ) +COMPREPLY=( ${COMPREPLY[@]} $( compgen -G $cur\*.[0-9ln] ) ) +return 0 +} +complete -F _para -o default para +complete -F _para -o default para_client diff --git a/client.c b/client.c new file mode 100644 index 00000000..1ece1950 --- /dev/null +++ b/client.c @@ -0,0 +1,318 @@ +/* + * Copyright (C) 1997-2006 Andre Noll + * + * 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 +#include +#include "client.cmdline.h" +#include "crypt.h" +#include "rc4.h" +#include +#include "net.h" +#include "string.h" + +/* A static variable for holding the line. */ +static char *line_read; + +struct gengetopt_args_info args_info; + +/* + * client log function + */ +void para_log(int ll, char* fmt,...) +{ + va_list argp; + FILE *outfd; + + /* ignore log message if loglevel is not high enough */ + if (ll < args_info.loglevel_arg) + return; + if (ll < WARNING) + outfd = stdout; + else + outfd = stderr; + va_start(argp, fmt); + vfprintf(stdout, fmt, argp); + va_end(argp); +} + +/* + * Read a string, and return a pointer to it. Returns NULL on EOF. + */ +static char *rl_gets(void) +{ + free(line_read); + /* Get a line from the user. */ + line_read = readline("para_client> "); + /* If the line has any text in it, save it on the history. */ + if (line_read && *line_read) + add_history(line_read); + return line_read; +} + +/* + * do several cleanups on sigint + */ +static void sigint_handler(__unused int i) +{ + rl_cleanup_after_signal(); + rl_reset_after_signal(); +} + +void get_options(int argc, char *argv[], + char **config_file, char **key_file) +{ + char *home; + static char default_key_file[_POSIX_PATH_MAX] = ""; + static char default_config_file[_POSIX_PATH_MAX] = ""; + struct stat statbuf; + int ret; + + cmdline_parser(argc, argv, &args_info); + if (!args_info.user_given) + args_info.user_arg = para_logname(); + if (!args_info.key_file_given) { + home = para_homedir(); + sprintf(default_key_file, "%s/.paraslash/key.%s", home, + args_info.user_arg); + free(home); + } + if (!args_info.config_file_given) { + home = para_homedir(); + sprintf(default_config_file, "%s/.paraslash/client.conf", + home); + free(home); + } + if (!args_info.config_file_given) + *config_file = default_config_file; + else + *config_file = args_info.config_file_arg; + ret = stat(*config_file, &statbuf); + if (ret && args_info.config_file_given) { + fprintf(stderr, "can not stat config file %s\n", + args_info.config_file_arg); + exit(EXIT_FAILURE); + } + if (!ret) + cmdline_parser_configfile(*config_file, &args_info, 0, 0, 0); + if (!args_info.key_file_given) + *key_file = default_key_file; + else + *key_file = args_info.key_file_arg; +} + +static RC4_KEY rc4_recv_key; +static RC4_KEY rc4_send_key; +static unsigned char rc4_buf[2 * RC4_KEY_LEN]; + +static void rc4_send(unsigned long len, const unsigned char *indata, unsigned char *outdata) +{ + RC4(&rc4_send_key, len, indata, outdata); +} + +static void rc4_recv(unsigned long len, const unsigned char *indata, unsigned char *outdata) +{ + RC4(&rc4_recv_key, len, indata, outdata); +} +void (*crypt_function_recv)(unsigned long len, const unsigned char *indata, unsigned char *outdata); +void (*crypt_function_send)(unsigned long len, const unsigned char *indata, unsigned char *outdata); + + +static void append_str(char **data, const char* append) +{ + if (*data) { + char *tmp = make_message("%s\n%s", *data, append); + free(*data); + *data = tmp; + } else + *data = para_strdup(append); +} + +/* + * MAIN + */ +int main(int argc, char *argv[]) +{ + + int sockfd, numbytes, i, interactive, received, ret; + struct hostent *he; + struct sockaddr_in their_addr; + char *command = NULL; + char buf[8192]; + char *auth_str; + char *key_file, *config_file; + long unsigned challenge_nr; + char *line; + + get_options(argc, argv, &config_file, &key_file); + if (args_info.loglevel_arg <= NOTICE) + cmdline_parser_print_version(); + PARA_INFO_LOG( + "current loglevel: %d\n" + "using config_file: %s\n" + "using key_file: %s\n" + "connecting to %s:%d\n", + args_info.loglevel_arg, + config_file, + key_file, + args_info.hostname_arg, + args_info.server_port_arg + ); + interactive = args_info.inputs_num == 0? 1 : 0; + if (interactive) { + PARA_NOTICE_LOG("%s", "no command, entering interactive mode\n"); + signal(SIGINT, sigint_handler); + } else { + /* not interactive, concat args */ + for (i = 0; i < args_info.inputs_num; i++) + append_str(&command, args_info.inputs[i]); + } +interactive_loop: + crypt_function_recv = NULL; + crypt_function_send = NULL; + if (interactive) { + int i = 0; + char *p; + + rl_save_prompt(); + rl_message("\n"); + rl_kill_full_line(0, 0); + rl_free_line_state(); + /* read a line via readline */ + line = rl_gets(); + if (!line) + return 0; + if (!line[0]) + goto interactive_loop; + p = line; + while (sscanf(p, "%200s%n", buf, &i) == 1) { + append_str(&command, buf); + p += i; + } + } + /* get the host info */ + PARA_NOTICE_LOG("getting host info of %s\n", + args_info.hostname_arg); + if (!(he = get_host_info(args_info.hostname_arg))) + exit(EXIT_FAILURE); + /* get new socket */ + if ((sockfd = get_socket()) < 0) + exit(EXIT_FAILURE); + /* init their_addr */ + init_sockaddr(&their_addr, args_info.server_port_arg, he); + /* Connect */ + PARA_NOTICE_LOG("connecting to %s...\n", + args_info.hostname_arg); + if (para_connect(sockfd, &their_addr) < 0) + exit(EXIT_FAILURE); + /* Receive Welcome message */ + if ((numbytes = recv_buffer(sockfd, buf, sizeof(buf))) < 0) + exit(EXIT_FAILURE); + /* send auth command */ + auth_str = make_message("auth %s%s", args_info.plain_given? "" : "rc4 ", + args_info.user_arg); + PARA_INFO_LOG("<-- %s--> %s\n", buf, auth_str); + if (send_buffer(sockfd, auth_str) < 0) + exit(EXIT_FAILURE); + /* receive challenge number */ + if ((numbytes = recv_buffer(sockfd, buf, sizeof(buf))) < 0) + exit(EXIT_FAILURE); + if (numbytes != 64) { + PARA_EMERG_LOG("did not receive valid challenge (got %i bytes)\n", + numbytes); + buf[numbytes] = '\0'; + PARA_ERROR_LOG("received the following instead: %s\n", buf); + goto write_out; + } + PARA_INFO_LOG("<-- [challenge (%i bytes)]\n", numbytes); + /* decrypt challenge number */ + ret = para_decrypt_challenge(key_file, &challenge_nr, (unsigned char *) buf, + numbytes); + if (ret < 0) { + PARA_EMERG_LOG("decrypt error (%d). Bad secret key?\n", ret); + exit(EXIT_FAILURE); + } + /* send decrypted challenge */ + PARA_INFO_LOG("--> %lu\n", challenge_nr); + if (send_va_buffer(sockfd, "%s%lu", CHALLENGE_RESPONSE_MSG, challenge_nr) < 0) + exit(EXIT_FAILURE); + /* Wait for approval */ + PARA_NOTICE_LOG("%s", "waiting for approval from server\n"); + if ((numbytes = recv_buffer(sockfd, buf, sizeof(buf))) < 0) + exit(EXIT_FAILURE); + PARA_INFO_LOG("++++ server info ++++\n%s\n++++ end of server " + "info ++++\n", buf); + /* Check if server has sent "Proceed" message */ + if (!strstr(buf, PROCEED_MSG)) { + PARA_EMERG_LOG("%s", "authentication failed\n"); + exit(EXIT_FAILURE); + } + if (numbytes >= PROCEED_MSG_LEN + 32) { + PARA_INFO_LOG("%s", "decrypting session key\n"); + if (para_decrypt_buffer(key_file, rc4_buf, + (unsigned char *)buf + PROCEED_MSG_LEN + 1, + numbytes - PROCEED_MSG_LEN - 1) < 0) { + PARA_EMERG_LOG("%s", "error receiving rc4 key\n"); + exit(EXIT_FAILURE); + } + RC4_set_key(&rc4_send_key, RC4_KEY_LEN, rc4_buf); + RC4_set_key(&rc4_recv_key, RC4_KEY_LEN, rc4_buf + RC4_KEY_LEN); + PARA_INFO_LOG("rc4 encrytion activated: %x:%x:%x:%x\n", + rc4_buf[0], rc4_buf[1], rc4_buf[2], rc4_buf[3]); + crypt_function_recv = rc4_recv; + crypt_function_send = rc4_send; + } + /* send command */ + PARA_INFO_LOG("--> %s\n", command); + if (send_buffer(sockfd, command) < 0) + exit(EXIT_FAILURE); + free(command); + command = NULL; + if (send_buffer(sockfd, EOC_MSG "\n") < 0) + exit(EXIT_FAILURE); + PARA_NOTICE_LOG("%s", "command sent.\n"); +write_out: + received = 0; + /* write server output to stdout */ + while ((numbytes = recv_bin_buffer(sockfd, buf, sizeof(buf))) > 0) { + int ret; + + if (!received && strstr(buf, AWAITING_DATA_MSG)) { + PARA_NOTICE_LOG("%s", "<-- awaiting data\n"); + PARA_NOTICE_LOG("%s", "--> sending stdin\n"); + while ((ret = read(STDIN_FILENO, buf, + sizeof(buf))) > 0) + send_bin_buffer(sockfd, buf, ret); + PARA_NOTICE_LOG("%s", "closing connection\n"); + numbytes = 1; + break; + } + received = 1; + if (write(STDOUT_FILENO, buf, numbytes) != numbytes) + break; + } + if (!numbytes) + PARA_NOTICE_LOG("%s", "connection closed by peer\n"); + close(sockfd); + if (interactive) + goto interactive_loop; + return 0; +} diff --git a/client.ggo b/client.ggo new file mode 100644 index 00000000..bc267d42 --- /dev/null +++ b/client.ggo @@ -0,0 +1,8 @@ +# file client.conf +option "hostname" i "ip or host to connect" string typestr="host" default="localhost" no +option "user" u "paraslash username" string typestr="username" default="" no +option "server_port" p "port to connect" int typestr="port" default="2990" no +option "key_file" k "(default='~/.paraslash/key.')" string typestr="filename" no +option "loglevel" l "set loglevel (0-6)" int typestr="number" default="5" no +option "config_file" c "(default='~/.paraslash/client.conf')" string typestr="filename" no +option "plain" - "request an uncrypted session" flag off diff --git a/close_on_fork.c b/close_on_fork.c new file mode 100644 index 00000000..74b41948 --- /dev/null +++ b/close_on_fork.c @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2005-2006 Andre Noll + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ + +/** \file close_on_fork.c manage a list of fds that should be closed on fork */ +#include "para.h" +#include "list.h" +#include "string.h" + +static struct list_head close_on_fork_list; +static int initialized; + +/** + * describes an element of the close-on-fork list + * + * \sa list.h + */ +struct close_on_fork { + /** the file descriptor which should be closed after fork() */ + int fd; + /** the position in the close-on-fork list */ + struct list_head node; +}; + +/** + * add one file descriptor to the close-on-fork list + * + * \param fd the file descriptor to add + */ +void add_close_on_fork_list(int fd) +{ + struct close_on_fork *cof = para_malloc(sizeof(struct close_on_fork)); + + if (!initialized) { + INIT_LIST_HEAD(&close_on_fork_list); + initialized = 1; + } + cof->fd = fd; + list_add(&cof->node, &close_on_fork_list); +} + + +/** + * delete one file descriptor from the close-on-fork list + * + * \param fd the file descriptor to delete + * + * Noop if \a fd does not belong to the close-on-fork list. + */ +void del_close_on_fork_list(int fd) +{ + struct close_on_fork *cof, *tmp; + + if (!initialized) + return; + list_for_each_entry_safe(cof, tmp, &close_on_fork_list, node) { + if (fd != cof->fd) + continue; + list_del(&cof->node); + free(cof); + } +} + +/** + * call close(3) for each fd in the close-on-fork list + */ +void close_listed_fds(void) +{ + struct close_on_fork *cof; + + if (!initialized) + return; + list_for_each_entry(cof, &close_on_fork_list, node) { + PARA_DEBUG_LOG("closing fd %d\n", cof->fd); + close(cof->fd); + } +} diff --git a/close_on_fork.h b/close_on_fork.h new file mode 100644 index 00000000..1bd0cd15 --- /dev/null +++ b/close_on_fork.h @@ -0,0 +1,4 @@ +/** \file close_on_fork.h exported symbols from close_on_fork.c */ +void del_close_on_fork_list(int fd); +void add_close_on_fork_list(int fd); +void close_listed_fds(void); diff --git a/command.c b/command.c new file mode 100644 index 00000000..81cb2831 --- /dev/null +++ b/command.c @@ -0,0 +1,1239 @@ +/* + * Copyright (C) 1997-2006 Andre Noll + * + * 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 /* mallinfo */ +#include /* gettimeofday */ +#include "crypt.h" +#include "server.cmdline.h" +#include "db.h" +#include "server.h" +#include "afs.h" +#include "send.h" +#include "rc4.h" +#include +#include "gcc-compat.h" +#include "error.h" +#include "net.h" +#include "daemon.h" +#include "string.h" + +void (*crypt_function_recv)(unsigned long len, const unsigned char *indata, + unsigned char *outdata) = NULL; +void (*crypt_function_send)(unsigned long len, const unsigned char *indata, + unsigned char *outdata) = NULL; +static RC4_KEY rc4_recv_key; +static RC4_KEY rc4_send_key; +static unsigned char rc4_buf[2 * RC4_KEY_LEN]; + +extern const char *status_item_list[NUM_STAT_ITEMS]; +extern struct misc_meta_data *mmd; +extern struct gengetopt_args_info conf; +extern struct dbtool dblist[]; +extern struct audio_format afl[]; +extern struct sender senders[]; +extern char *user_list; +struct sockaddr_in *in_addr; + +static int com_si(int, int, char **); +static int com_version(int, int, char **); +static int com_sb(int, int, char **); +static int com_sc(int, int, char **); +static int com_stat(int, int, char **); +static int com_help(int, int, char **); +static int com_hup(int, int, char **); +static int com_term(int, int, char **); +static int com_play(int, int, char **); +static int com_stop(int, int, char **); +static int com_pause(int, int, char **); +static int com_next(int, int, char **); +static int com_nomore(int, int, char **); +static int com_cdt(int, int, char **); +static int com_ff(int, int, char **); +static int com_jmp(int, int, char **); +static int com_sender(int, int, char **); + + +/* commands that are handled by the server itself */ +static struct server_command cmd_struct[] = { +{ +.name = "cdt", +.handler = com_cdt, +.perms = DB_READ | DB_WRITE, +.description = "change database tool", +.synopsis = "cdt [name_of_new_dbtool]", +.help = +"Deactivate current dbtool and activate name_of_new_dbtool. If no\n" +"argument was given, print the current database tool.\n" +}, + +{ +.name = "ff", +.handler = com_ff, +.perms = AFS_READ | AFS_WRITE, +.description = "jmp amount of time forwards or backwards " + "in current audio file", +.synopsis = "ff n[-]", +.help = + +"\tSet the 'R' (reposition request) bit of the afs status flags\n" +"\tand enqueue a request to jump n seconds forwards or backwards\n" +"\tin the current audio file.\n" +"\n" +"EXAMPLE\n" +"\n" +"\t\tff 30-\n" +"\n" +"\tjumps 30 seconds backwards.\n" + +}, + +{ +.name = "help", +.handler = com_help, +.perms = 0, +.description = "print help text", +.synopsis = "help [command]", +.help = + +"Without any arguments, help prints a list of availible commands. When\n" +"issued with a command name as first argument, print out a description\n" +"for that command.\n" + +}, + +{ +.name = "hup", +.handler = com_hup, +.perms = AFS_WRITE, +.description = "force reload of config file and log file", +.synopsis = "hup", +.help = + +"After rereading the config file, a signal is sent to all children\n" +"which forces them to close/reopen the log file.\n" + +}, + +{ +.name = "jmp", +.handler = com_jmp, +.perms = AFS_READ | AFS_WRITE, +.description = "jmp to given position in current audio file", +.synopsis = "jmp [n]", +.help = + +"\tSet the 'R' (reposition request) bit of the afs status flags\n" +"\tand enqueue a request to jump to n% of the current audio file,\n" +"\twhere 0 <= n <= 100.\n" + +}, + +{ +.name = "next", +.handler = com_next, +.perms = AFS_READ | AFS_WRITE, +.description = "skip rest of current audio file", +.synopsis = "next", +.help = + +"\tSet the 'N' (next audio file) bit of the afs status flags. When\n" +"\tplaying, change audio file immediately. Equivalent to stop\n" +"\tif paused, NOP if stopped.\n" + + +}, + +{ +.name = "nomore", +.handler = com_nomore, +.perms = AFS_READ | AFS_WRITE, +.description = "stop playing after current audio file", +.synopsis = "nomore", +.help = + +"Set the 'O' (no more) bit of the afs status flags. This instructs\n" +"para_server to clear the 'P' (playing) bit as soon as it encounters\n" +"the 'N' (next audio file) bit being set.\n" +"\n" +"Use this command instead of stop if you don't like\n" +"sudden endings.\n" + +}, + +{ +.name ="pause", +.handler = com_pause, +.perms = AFS_READ | AFS_WRITE, +.description = "pause current audio file", +.synopsis = "pause", +.help = + +"\tClear the 'P' (playing) bit of the afs status flags.\n" + +}, + +{ +.name = "play", +.handler = com_play, +.perms = AFS_READ | AFS_WRITE, +.description = "start playing or resume playing when paused", +.synopsis = "play", +.help = + +"\tSet the 'P' (playing) bit of the afs status flags. This\n" +"\tresults in starting/continuing to stream.\n" + +}, + +{ +.name = "sb", +.handler = com_sb, +.perms = AFS_READ, +.description = "print status bar for current audio file", +.synopsis = "sb [n]", +.help = + +"Without any arguments, sb continuously prints a status bar of the form\n" +"\n" +" 12:34 [56:12] (56%) filename\n" +"\n" +"indicating playing time, remaining time, percentage and the name of\n" +"the file beeing streamed. Use the optional number n to let stat exit\n" +"after having displayed the status bar n times.\n" + +}, +{ +.name = "sc", +.handler = com_sc, +.perms = AFS_READ, +.description = "print name of audio file whenever it changes", +.synopsis = "sc [n]", +.help = + +"\tsc prints exactly one line (the filename of the audio file\n" +"\tbeing played) whenever the audio file changes. Stops after\n" +"\tn iterations, or never if n is not specified.\n" + +}, +{ +.name = "sender", +.handler = com_sender, +.perms = AFS_READ | AFS_WRITE, +.description = "control paraslash internal senders", +.synopsis = "sender [s cmd [arguments]]", +.help = + +"send command cmd to sender s. cmd may be one of the following:\n" +"help, on, off, add, delete, allow, or deny. Note that not all senders\n" +"support each command. Try e.g. 'para_client sender http help' for\n" +"more information about the http sender. If no argument is given,\n" +"print out a list of all senders that are compiled in.\n" + +}, +{ +.name = "si", +.handler = com_si, +.perms = 0, +.description = "print server info", +.synopsis = "si", +.help = +"Print server uptime and other information.\n" +}, + +{ +.name = "stat", +.handler = com_stat, +.perms = AFS_READ, +.description = "print status info for current audio file", +.synopsis = "stat [n]", +.help = + +"\tWithout any arguments, stat continuously prints status messages\n" +"\tof the audio file being streamed. Use the optional number n\n" +"\tto let stat exit after having displayed status n times.\n" + +}, + +{ +.name = "stop", +.handler = com_stop, +.perms = AFS_READ | AFS_WRITE, +.description = "stop playing", +.synopsis = "stop", +.help = + +"\tClear the 'P' (play) bit and set the 'N' bit of the afs status\n" +"\tflags.\n" + +}, +{ +.name = "term", +.handler = com_term, +.perms = AFS_READ | AFS_WRITE, +.description = "terminate para_server", +.synopsis = "term", +.help = + +"Shuts down the server. Instead of this command, you can also send\n" +"SIGINT or SIGTERM. It should never be necessary to send SIGKILL.\n" + +}, +{ +.name = "version", +.handler = com_version, +.perms = 0, +.description = "print server's version", +.synopsis = "version", +.help = +"Show version and other info\n" +}, +/* this indicates the end of the list. Do not touch. */ +{ +.name = NULL, +} +}; + +static void dummy(__unused int s) +{} + +static void mmd_dup(struct misc_meta_data *new_mmd) +{ + mmd_lock(); + *new_mmd = *mmd; + mmd_unlock(); +} + +/* + * compute human readable string containing + * afs_status for given integer value + */ +static char *afs_status_tohuman(unsigned int flags) +{ + if (flags & AFS_PLAYING) + return para_strdup("playing"); + else if (flags & AFS_NEXT) + return para_strdup("stopped"); + else + return para_strdup("paused"); +} + + +/* + * return human readable permission string. Never returns NULL. + */ +char *cmd_perms_itohuman(unsigned int perms) +{ + char *msg = para_malloc(7 * sizeof(char)); + + msg[0] = perms & DB_READ? 'd' : '-'; + msg[1] = perms & DB_WRITE? 'D' : '-'; + msg[2] = perms & AFS_READ? 'a' : '-'; + msg[3] = perms & AFS_WRITE? 'A' : '-'; + msg[4] = '\0'; + return msg; +} + +/* + * Never returns NULL. + */ +static char *afs_get_status_flags(unsigned int flags) +{ + char *msg = para_malloc(5 * sizeof(char)); + + msg[0] = (flags & AFS_PLAYING)? 'P' : '_'; + msg[1] = (flags & AFS_NOMORE)? 'O' : '_'; + msg[2] = (flags & AFS_NEXT)? 'N' : '_'; + msg[3] = (flags & AFS_REPOS)? 'R' : '_'; + msg[4] = '\0'; + return msg; +} + +/* + * compute status bar string. Never returns NULL + */ +char *get_sb_string(struct misc_meta_data *nmmd) +{ + char *base, *ret; + long long unsigned secs = 0, rsecs = 0, percent = 0; + + base = para_basename(nmmd->filename); + if (!base) + return para_strdup(""); + if (!base[0]) + return base; + if (nmmd->chunks_total) { + secs = (long long) nmmd->seconds_total * nmmd->chunks_sent + / nmmd->chunks_total; + rsecs = (long long) nmmd->seconds_total * + (nmmd->chunks_total - nmmd->chunks_sent) + / nmmd->chunks_total; + percent = 100 * ((nmmd->chunks_sent + 5) / 10) + / ((nmmd->chunks_total + 5) / 10); + } + ret = make_message("%llu:%02llu [%llu:%02llu] (%llu%%) %s", + secs / 60, secs % 60, + rsecs / 60, rsecs % 60, + percent, + base + ); + free(base); + return ret; +} + +static char *get_status(struct misc_meta_data *nmmd) +{ + char *bar, *ret, mtime[30] = ""; + char *status, *flags; /* afs status info */ + char *ut = uptime_str(); + long offset = (nmmd->offset + 500) / 1000; + struct timeval now; + struct tm mtime_tm; + + if (nmmd->audio_format >= 0) { + localtime_r(&nmmd->mtime, &mtime_tm); + strftime(mtime, 29, "%a %b %d %Y", &mtime_tm); + } + /* report real status */ + status = afs_status_tohuman(nmmd->afs_status_flags); + flags = afs_get_status_flags(nmmd->afs_status_flags); + bar = para_basename(nmmd->filename); + gettimeofday(&now, NULL); + ret = make_message( + "%s:%lu\n" "%s:%s\n" "%s:%i\n" "%s:%u\n" + "%s:%s\n" "%s:%s\n" "%s:%s\n" "%s:%s\n" + "%s:%li\n" "%s:%s\n" "%s" "%s" + "%s:%s\n" "%s:%lu.%lu\n" "%s:%lu.%lu\n", + status_item_list[SI_FILE_SIZE], nmmd->size / 1024, + status_item_list[SI_MTIME], mtime, + status_item_list[SI_LENGTH], nmmd->seconds_total, + status_item_list[SI_NUM_PLAYED], nmmd->num_played, + + status_item_list[SI_STATUS_BAR], bar ? bar : "(none)", + status_item_list[SI_STATUS], status, + status_item_list[SI_STATUS_FLAGS], flags, + status_item_list[SI_DBTOOL], dblist[nmmd->dbt_num].name, + + status_item_list[SI_OFFSET], offset, + status_item_list[SI_FORMAT], audio_format_name(nmmd->audio_format), + nmmd->dbinfo, + nmmd->audio_file_info, + + status_item_list[SI_UPTIME], ut, + status_item_list[SI_STREAM_START], nmmd->stream_start.tv_sec, + nmmd->stream_start.tv_usec, + status_item_list[SI_CURRENT_TIME], now.tv_sec, now.tv_usec + + ); + free(bar); + free(flags); + free(status); + free(ut); + return ret; +} + +static int check_sender_args(int argc, char **argv, struct sender_command_data *scd) +{ + int i; + /* this has to match sender.h */ + const char *subcmds[] = {"add", "delete", "allow", "deny", "on", "off", NULL}; + + scd->sender_num = -1; + if (argc < 0) + return -E_COMMAND_SYNTAX; + for (i = 0; senders[i].name; i++) + if (!strcmp(senders[i].name, argv[0])) + break; +// PARA_DEBUG_LOG("%d:%s\n", argc, argv[0]); + if (!senders[i].name) + return -E_COMMAND_SYNTAX; + scd->sender_num = i; + for (i = 0; subcmds[i]; i++) + if (!strcmp(subcmds[i], argv[1])) + break; + if (!subcmds[i]) + return -E_COMMAND_SYNTAX; + scd->cmd_num = i; +// scd->self = *in_addr; + mmd_lock(); + if (!senders[scd->sender_num].client_cmds[scd->cmd_num]) { + mmd_unlock(); + return -E_SENDER_CMD; + } + mmd_unlock(); + switch (scd->cmd_num) { + case SENDER_ON: + case SENDER_OFF: + if (argc != 1) + return -E_COMMAND_SYNTAX; + break; + case SENDER_DENY: + case SENDER_ALLOW: + if (argc != 2 && argc != 3) + return -E_COMMAND_SYNTAX; + if (!inet_aton(argv[2], &scd->addr)) + return -E_COMMAND_SYNTAX; + scd->netmask = 32; + if (argc == 3) { + scd->netmask = atoi(argv[3]); + if (scd->netmask < 0 || scd->netmask > 32) + return -E_COMMAND_SYNTAX; + } + break; + case SENDER_ADD: + case SENDER_DELETE: + if (argc != 2 && argc != 3) + return -E_COMMAND_SYNTAX; + if (!inet_aton(argv[2], &scd->addr)) + return -E_COMMAND_SYNTAX; + scd->port = -1; + if (argc == 3) { + scd->port = atoi(argv[3]); + if (scd->port < 0 || scd->port > 65535) + return -E_COMMAND_SYNTAX; + } + break; + default: + return -E_COMMAND_SYNTAX; + } + return 1; +} + +static int com_sender(int fd, int argc, char **argv) +{ + int i, ret; + struct sender_command_data scd; + + if (!argc) { + char *msg = NULL; + for (i = 0; senders[i].name; i++) { + char *tmp = make_message("%s%s\n", + msg? msg : "", senders[i].name); + free(msg); + msg = tmp; + } + ret = send_buffer(fd, msg); + free(msg); + return ret; + } + ret = check_sender_args(argc - 1, argv + 1, &scd); + if (ret < 0) { + char *msg; + if (scd.sender_num < 0) + return ret; + msg = senders[scd.sender_num].help(); + send_buffer(fd, msg); + free(msg); + return 1; + } + for (i = 0; i < 10; i++) { + mmd_lock(); + if (mmd->sender_cmd_data.cmd_num >= 0) { + mmd_unlock(); + usleep(100 * 1000); + continue; + } + mmd->sender_cmd_data = scd; + mmd_unlock(); + break; + } + return (i < 10)? 1 : -E_LOCK; +} + +/* server info */ +static int com_si(int fd, int argc, __unused char **argv) +{ + int i, ret; + char *ut; + char *dbtools = NULL, *sender_info = NULL, *sender_list = NULL; + struct mallinfo mi = mallinfo(); + + if (argc) + return -E_COMMAND_SYNTAX; + mmd_lock(); + for (i = 0; dblist[i].name; i++) { + dbtools = para_strcat(dbtools, dblist[i].name); + dbtools = para_strcat(dbtools, " "); + } + for (i = 0; senders[i].name; i++) { + char *info = senders[i].info(); + sender_info = para_strcat(sender_info, info); + free(info); + sender_list = para_strcat(sender_list, senders[i].name); + sender_list = para_strcat(sender_list, " "); + } + ut = uptime_str(); + ret = send_va_buffer(fd, "up: %s\nplayed: %u\n" + "pid: %d\n" + "mallinfo: %d\n" + "connections (active/accepted/total): %u/%u/%u\n" + "current loglevel: %i\n" + "supported database tools: %s\n" + "supported audio formats: %s\n" + "supported senders: %s\n" + "%s", + ut, mmd->num_played, + getppid(), + mi.arena / 1024, + mmd->active_connections, + mmd->num_commands, + mmd->num_connects, + conf.loglevel_arg, + dbtools, + SUPPORTED_AUDIO_FORMATS, + sender_list, + sender_info + ); + mmd_unlock(); + free(ut); + free(dbtools); + free(sender_list); + free(sender_info); + return ret; +} + +/* version */ +static int com_version(int socket_fd, int argc, __unused char **argv) +{ + if (argc) + return -E_COMMAND_SYNTAX; + return send_buffer(socket_fd, "para_server-" VERSION ", \"" CODENAME "\"\n" + COPYRIGHT "\n" + "built: " BUILD_DATE "\n" + SYSTEM ", " CC_VERSION "\n" + ); +} + +/* sc */ +static int com_sc(int socket_fd, int argc, char **argv) +{ + char *name = NULL; + int ret, old = 0, count = -1; /* print af change forever */ + + if (argc) + count = atoi(argv[1]); +repeat: + mmd_lock(); + if (old != mmd->num_played) { + old = mmd->num_played; + name = para_strdup(mmd->filename); + } + mmd_unlock(); + if (name) { + ret = send_va_buffer(socket_fd, "%s\n", name); + free(name); + name = NULL; + if (ret < 0) + return ret; + if (argc && !--count) + return 1; + } + usleep(500000); + goto repeat; +} + +/* sb */ +static int com_sb(int socket_fd, int argc, char **argv) +{ + char *sb; + int ret, nr = -1; /* status bar will be printed that many + * times. Negative value means: print + * forever + */ + if (argc) + nr = atoi(argv[1]); + while (nr) { + mmd_lock(); + sb = get_sb_string(mmd); + mmd_unlock(); + ret = send_va_buffer(socket_fd, "%s\n", sb); + free(sb); + if (ret < 0) + return ret; + if (nr == 1) + return 1; + usleep(500000); + if (nr > 0) + nr--; + } + return 1; +} + +/* stat */ +static int com_stat(int socket_fd, int argc, char **argv) +{ +// char *old_stat = NULL, *old_dbinfo = NULL; + int ret, num = 0;/* status will be printed that many + * times. num <= 0 means: print forever + */ + struct misc_meta_data tmp, *nmmd = &tmp; + char *s; + + signal(SIGUSR1, dummy); + + if (argc) + num = atoi(argv[1]); + for (;;) { + + mmd_dup(nmmd); + s = get_status(nmmd); + ret = send_buffer(socket_fd, s); + free(s); + if (ret < 0) + goto out; + ret = 1; + if (num == 1) + goto out; + usleep(500000 * 100); + } +out: + return ret; +} + +static int send_description(int fd, struct server_command *cmd, const char *handler, int num) +{ + int ret, i; + + for (i = 1; cmd->name && (!num || i <= num); cmd++, i++) { + char *perms = cmd_perms_itohuman(cmd->perms); + ret = send_va_buffer(fd, "%s\t%s\t%s\t%s\n", cmd->name, + handler, + perms, + cmd->description); + free(perms); + if (ret < 0) + return ret; + } + return 1; +} + +/* always returns string that must be freed by the caller in handeler */ +static struct server_command *get_cmd_ptr(char *name, char **handler) +{ + struct server_command *cmd = cmd_struct; + + for (cmd = cmd_struct; cmd->name; cmd++) + if (!strcmp(cmd->name, name)) { + if (handler) + *handler = para_strdup("para_server"); /* server commands */ + return cmd; + } + /* not found, look for dbtool commands */ + mmd_lock(); + if (handler) + *handler = make_message("the %s database tool", dblist[mmd->dbt_num].name); + cmd = dblist[mmd->dbt_num].cmd_list; + mmd_unlock(); + for (; cmd->name; cmd++) + if (!strcmp(cmd->name, name)) + return cmd; + return NULL; +} + +/* help */ +static int com_help(int fd, int argc, char **argv) +{ + struct server_command *cmd; + char *perms, *handler; + int ret; + + if (!argc) { + /* no argument given, print list of commands */ + if ((ret = send_description(fd, cmd_struct, "server", 0)) < 0) + return ret; + mmd_lock(); + handler = para_strdup(dblist[mmd->dbt_num].name); + cmd = dblist[mmd->dbt_num].cmd_list; + mmd_unlock(); + ret = send_description(fd, cmd, handler, 0); + free(handler); + return ret; + } + /* argument given for help */ + cmd = get_cmd_ptr(argv[1], &handler); + if (!cmd) { + free(handler); + return -E_BAD_CMD; + } + perms = cmd_perms_itohuman(cmd->perms); + ret = send_va_buffer(fd, + "NAME\n\t%s - %s\n" + "SYNOPSIS\n\t para_client %s\n" + "DESCRIPTION\n%s\n" + "HANDLER\n" + "This command is handled by %s.\n\n" + "PERMISSIONS\n" + "Needed privileges for %s: %s\n", + argv[1], + cmd->description, + cmd->synopsis, + cmd->help, + handler, + argv[1], + perms + ); + free(perms); + free(handler); + return ret; +} + +/* hup */ +static int com_hup(__unused int socket_fd, int argc, __unused char **argv) +{ + if (argc) + return -E_COMMAND_SYNTAX; + kill(getppid(), SIGHUP); + return 1; +} + +/* term */ +static int com_term(__unused int socket_fd, int argc, __unused char **argv) +{ + if (argc) + return -E_COMMAND_SYNTAX; + kill(getppid(), SIGTERM); + return 1; +} + +static int com_play(__unused int socket_fd, int argc, __unused char **argv) +{ + if (argc) + return -E_COMMAND_SYNTAX; + mmd_lock(); + mmd->new_afs_status_flags |= AFS_PLAYING; + mmd->new_afs_status_flags &= ~AFS_NOMORE; + mmd_unlock(); + return 1; + +} + +/* stop */ +static int com_stop(__unused int socket_fd, int argc, __unused char **argv) +{ + if (argc) + return -E_COMMAND_SYNTAX; + mmd_lock(); + mmd->new_afs_status_flags &= ~AFS_PLAYING; + mmd->new_afs_status_flags &= ~AFS_REPOS; + mmd->new_afs_status_flags |= AFS_NEXT; + mmd_unlock(); + return 1; +} + +/* pause */ +static int com_pause(__unused int socket_fd, int argc, __unused char **argv) +{ + if (argc) + return -E_COMMAND_SYNTAX; + mmd_lock(); + if (!afs_paused()) + mmd->events++; + mmd->new_afs_status_flags &= ~AFS_PLAYING; + mmd->new_afs_status_flags &= ~AFS_NEXT; + mmd_unlock(); + return 1; +} + +static int com_cdt(int fd, int argc, char **argv) +{ + int i, ret; + + if (!argc) { + char *dbtool; + mmd_lock(); + dbtool = para_strdup(dblist[mmd->dbt_num].name); + mmd_unlock(); + ret = send_va_buffer(fd, "%s\n", dbtool); + free(dbtool); + return ret; + } + for (i = 0; dblist[i].name; i++) { + if (strcmp(dblist[i].name, argv[1])) + continue; + mmd_lock(); + mmd->dbt_change = i; + mmd->events++; + mmd_unlock(); + return 1; + } + return -E_BAD_DBTOOL; +} + +/* next */ +static int com_next(__unused int socket_fd, int argc, __unused char **argv) +{ + if (argc) + return -E_COMMAND_SYNTAX; + mmd_lock(); + mmd->new_afs_status_flags |= AFS_NEXT; + mmd_unlock(); + return 1; +} + +/* nomore */ +static int com_nomore(__unused int socket_fd, int argc, __unused char **argv) +{ + if (argc) + return -E_COMMAND_SYNTAX; + mmd_lock(); + if (afs_playing() || afs_paused()) + mmd->new_afs_status_flags |= AFS_NOMORE; + mmd_unlock(); + return 1; +} + +/* ff */ +static int com_ff(__unused int socket_fd, int argc, char **argv) +{ + long promille; + int ret, backwards = 0; + unsigned i; + char c; + + if (!argc) + return -E_COMMAND_SYNTAX; + if (!(ret = sscanf(argv[1], "%u%c", &i, &c))) + return -E_COMMAND_SYNTAX; + if (ret > 1 && c == '-') + backwards = 1; /* jmp backwards */ + mmd_lock(); + ret = -E_NO_AUDIO_FILE; + if (!mmd->chunks_total || !mmd->seconds_total) + goto out; + promille = (1000 * mmd->current_chunk) / mmd->chunks_total; + if (backwards) + promille -= 1000 * i / mmd->seconds_total; + else + promille += 1000 * i / mmd->seconds_total; + if (promille < 0) + promille = 0; + if (promille > 1000) { + mmd->new_afs_status_flags |= AFS_NEXT; + goto out; + } + mmd->repos_request = (mmd->chunks_total * promille) / 1000; + mmd->new_afs_status_flags |= AFS_REPOS; + mmd->new_afs_status_flags &= ~AFS_NEXT; + mmd->events++; + ret = 1; +out: + mmd_unlock(); + return ret; +} + +/* jmp */ +static int com_jmp(__unused int socket_fd, int argc, char **argv) +{ + long unsigned int i; + int ret; + + if (!argc) + return -E_COMMAND_SYNTAX; + if (sscanf(argv[1], "%lu", &i) <= 0) + return -E_COMMAND_SYNTAX; + mmd_lock(); + ret = -E_NO_AUDIO_FILE; + if (!mmd->chunks_total) + goto out; + if (i > 100) + i = 100; + PARA_INFO_LOG("jumping to %lu%%\n", i); + mmd->repos_request = (mmd->chunks_total * i + 50)/ 100; + PARA_INFO_LOG("sent: %lu, offset before jmp: %lu\n", + mmd->chunks_sent, mmd->offset); + mmd->new_afs_status_flags |= AFS_REPOS; + mmd->new_afs_status_flags &= ~AFS_NEXT; + ret = 1; + mmd->events++; +out: + mmd_unlock(); + return ret; +} + +/* + * check if perms are sufficient to exec a command having perms cmd_perms. + * Returns 0 if perms are sufficient, -E_PERM otherwise. + */ +static int check_perms(unsigned int perms, struct server_command *cmd_ptr) +{ + PARA_DEBUG_LOG("%s", "checking permissions\n"); + return (cmd_ptr->perms & perms) < cmd_ptr->perms ? -E_PERM : 0; +} + +/* + * Parse first string from *cmd and lookup in table of valid commands. + * On error, NULL is returned. + */ +static struct server_command *parse_cmd(const char *cmdstr) +{ + char buf[255]; + int n = 0; + + sscanf(cmdstr, "%200s%n", buf, &n); + if (!n) + return NULL; + buf[n] = '\0'; + return get_cmd_ptr(buf, NULL); +} + +long int para_rand(long unsigned max) +{ + return (long int) ((max + 0.0) * (random() / (RAND_MAX + 1.0))); +} + + +/* Open user_list file, returns pointer to opened file on success, + * NULL on errors + */ +static FILE *open_user_list(char *file) +{ + PARA_DEBUG_LOG("opening user list %s\n", file); + return fopen(file, "r"); +} + +/* + * lookup user in user_list file. Fills in a user struct containing + * filename of the user's public key as well as the permissions of that user. + * Returns 1 on success, 0 if user does not exist and < 0 on errors. + */ +static int get_user(struct user *user) { + FILE *file_ptr; + char *char_ptr; + char line[MAXLINE]; + /* keyword, user, key, perms */ + char w[MAXLINE], n[MAXLINE], k[MAXLINE], p[MAXLINE], tmp[4][MAXLINE]; + int num; + + file_ptr = open_user_list(user_list); + if (!file_ptr) + return -E_USERLIST; + while (fgets(line, MAXLINE, file_ptr)) { +// PARA_DEBUG_LOG("%s: Read line (%i bytes) " +// "from config file\n", __func__, strlen(line)); + if (sscanf(line,"%200s %200s %200s %200s", w, n, k, p) < 3) + continue; + if (!strcmp(w, "user") && !strcmp(user->name, n)) { + PARA_DEBUG_LOG("found entry for %s\n", n); + strcpy(user->name, n); + strcpy(user->pubkey_file, k); + user->perms = 0; + char_ptr = p; + num = sscanf(char_ptr, "%200[A-Z_],%200[A-Z_],%200[A-Z_],%200[A-Z_]", + tmp[0], tmp[1], tmp[2], tmp[3]); + PARA_DEBUG_LOG("found %i perm entries\n", + num); + user->perms = 0; + while (num > 0) { + num--; + //PARA_DEBUG_LOG("%s: tmp[%i]=%s\n", __func__, + // num, tmp[num]); + if (!strcmp(tmp[num], "AFS_READ")) + user->perms = + user->perms | AFS_READ; + else if (!strcmp(tmp[num], "AFS_WRITE")) + user->perms = + user->perms | AFS_WRITE; + else if (!strcmp(tmp[num], "DB_READ")) + user->perms = user->perms | DB_READ; + else if (!strcmp(tmp[num], "DB_WRITE")) + user->perms = user->perms | DB_WRITE; + else /* unknown permission */ + PARA_WARNING_LOG("unknown permission:" + "%s\n", tmp[num]); + } + fclose(file_ptr); + return 1; + } + } + fclose(file_ptr); + return 0; +} + +static void init_rc4_keys(void) +{ + int i; + + for (i = 0; i < 2 * RC4_KEY_LEN; i++) + rc4_buf[i] = para_rand(256); + PARA_DEBUG_LOG("rc4 keys initialized (%u:%u)\n", + (unsigned char) rc4_buf[0], + (unsigned char) rc4_buf[RC4_KEY_LEN]); + RC4_set_key(&rc4_recv_key, RC4_KEY_LEN, rc4_buf); + RC4_set_key(&rc4_send_key, RC4_KEY_LEN, rc4_buf + RC4_KEY_LEN); +} + +static void rc4_recv(unsigned long len, const unsigned char *indata, unsigned char *outdata) +{ + RC4(&rc4_recv_key, len, indata, outdata); +} +static void rc4_send(unsigned long len, const unsigned char *indata, unsigned char *outdata) +{ + RC4(&rc4_send_key, len, indata, outdata); +} + + + + +int handle_connect(int fd, struct sockaddr_in *addr) +{ + int numbytes, ret, argc, use_rc4 = 0; + char buf[STRINGSIZE]; + unsigned char crypt_buf[MAXLINE]; + struct user u; + struct server_command *cmd = NULL; + long unsigned challenge_nr, chall_response; + char **argv = NULL; + char *p, *command = NULL; + + signal(SIGCHLD, SIG_IGN); + signal(SIGINT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGHUP, SIG_DFL); + signal(SIGUSR1, SIG_IGN); + + in_addr = addr; + challenge_nr = random(); + /* send Welcome message */ + ret = send_va_buffer(fd, "This is para_server, version " VERSION ".\n" ); + if (ret < 0) + goto err_out; + /* recv auth request line */ + ret = recv_buffer(fd, buf, sizeof(buf)); + if (ret < 0) + goto err_out; + if (ret <= 6) { + ret = -E_AUTH; + goto err_out; + } + numbytes = ret; + ret = -E_AUTH; + if (strncmp(buf, "auth ", 5)) + goto err_out; + + if (numbytes < 9 || strncmp(buf, "auth rc4 ", 9)) + strcpy(u.name, buf + 5); /* client version < 0.2.6 */ + else { + strcpy(u.name, buf + 9); /* client version >= 0.2.6 */ + use_rc4 = 1; + } +// strcpy(u.name, buf + 5); /* ok, but ugly */ + PARA_DEBUG_LOG("received %s request for user %s\n", + use_rc4? "rc4" : "auth", u.name); + /* lookup user in list file */ + if ((ret = get_user(&u)) < 0) + goto err_out; + if (!ret) { /* user not found */ + PARA_WARNING_LOG("auth request for unknown user %s\n", u.name); + ret = -E_BAD_USER; + goto err_out; + } + ret = para_encrypt_challenge(u.pubkey_file, challenge_nr, crypt_buf); + if (ret <= 0) + goto err_out; + numbytes = ret; + PARA_DEBUG_LOG("sending %d byte challenge\n", numbytes); + /* We can't use send_buffer here since buf may contain null bytes */ + ret = send_bin_buffer(fd,(char *) crypt_buf, numbytes); + if (ret < 0) + goto err_out; + /* recv decrypted number */ + numbytes = recv_buffer(fd, buf, sizeof(buf)); + ret = numbytes; + if (ret < 0) + goto err_out; + ret = -E_AUTH; + if (!numbytes) + goto err_out; + if (sscanf(buf, CHALLENGE_RESPONSE_MSG "%lu", &chall_response) < 1 + || chall_response != challenge_nr) + goto err_out; + /* auth successful. Send 'Proceed' message */ + PARA_INFO_LOG("good auth for %s (%lu)\n", u.name, challenge_nr); + sprintf(buf, "%s", PROCEED_MSG); + if (use_rc4) { + init_rc4_keys(); + ret = para_encrypt_buffer(u.pubkey_file, rc4_buf, 2 * RC4_KEY_LEN, + (unsigned char *)buf + PROCEED_MSG_LEN + 1); + if (ret <= 0) + goto err_out; + numbytes = ret + strlen(PROCEED_MSG) + 1; + } else + numbytes = strlen(buf); + ret = send_bin_buffer(fd, buf, numbytes); + if (ret < 0) + goto err_out; + if (use_rc4) { + crypt_function_recv = rc4_recv; + crypt_function_send = rc4_send; + PARA_INFO_LOG("%s", "rc4 encrytion activated\n"); + } + /* read command */ + while ((numbytes = recv_buffer(fd, buf, sizeof(buf))) > 0) { +// PARA_INFO_LOG("recvd: %s (%d)\n", buf, numbytes); + ret = -E_COMMAND_SYNTAX; + if (command && numbytes + strlen(command) > STRINGSIZE) /* DOS */ + goto err_out; + command = para_strcat(command, buf); + if ((p = strstr(command, EOC_MSG))) { + *p = '\0'; + break; + } + } + ret = numbytes; + if (ret < 0) + goto err_out; + ret = -E_BAD_CMD; + /* parse command */ + if (!(cmd = parse_cmd(command))) + goto err_out; + /* valid command, check permissions */ + ret = check_perms(u.perms, cmd); + if (ret < 0) + goto err_out; + /* valid command and sufficient perms */ + alarm(0); + argc = split_args(command, &argv, '\n'); + argv[0] = cmd->name; + mmd_lock(); + mmd->num_commands++; + mmd_unlock(); + PARA_NOTICE_LOG("calling com_%s() for %s@%s\n", cmd->name, u.name, + inet_ntoa(addr->sin_addr)); + ret = cmd->handler(fd, argc, argv); + if (ret >= 0) { + ret = EXIT_SUCCESS; + goto out; + } +err_out: + if (ret != -E_SEND && ret != -E_RECV) { + PARA_NOTICE_LOG("%s\n", PARA_STRERROR(-ret)); + send_va_buffer(fd, "%s\n", PARA_STRERROR(-ret)); + } + ret = EXIT_FAILURE; +out: + free(command); + free(argv); + mmd_lock(); + if (cmd && (cmd->perms & DB_WRITE) && ret >= 0) + mmd->events++; + mmd->active_connections--; + mmd_unlock(); + return ret; +} diff --git a/compress.c b/compress.c new file mode 100644 index 00000000..da79b721 --- /dev/null +++ b/compress.c @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2005-2006 Andre Noll + * + * 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 + */ + +#include "gcc-compat.h" +#include "para.h" +#include "compress_filter.cmdline.h" +#include "list.h" +#include "filter.h" +#include "string.h" + +/** how fine-grained the gain is */ +#define GAINSHIFT 10 +/** the size of the output data buffer */ +#define COMPRESS_CHUNK_SIZE 40960 + +/** data specific to the compress filter */ +struct private_compress_data { + /** an array holding the previous peak values */ + int *peaks; + /** current bucket number to be modified */ + unsigned pn; + /** number of times clipping occured */ + unsigned clip; + /** the current multiplier */ + int current_gain; + /** the target multiplier */ + int target_gain; + /** pointer to the configuration data for this instance of the compress filter */ + struct gengetopt_args_info *conf; +}; + +static ssize_t compress(char *inbuf, size_t inbuf_len, struct filter_node *fn) +{ + int16_t *audio = (int16_t *) inbuf, *ip = audio, *op; + int peak = 1, pos = 0, i, gr, gf, gn; + size_t length = MIN((inbuf_len / 2) * 2, (fn->bufsize - fn->loaded) / 2 * 2); + struct private_compress_data *pcd = fn->private_data; + + if (!length) + return 0; + /* determine peak's value and position */ + for (i = 0; i < length / 2; i++, ip++) { + int val = ABS(*ip); + if (val > peak) { + peak = val; + pos = i; + } + } + pcd->peaks[pcd->pn] = peak; + for (i = 0; i < pcd->conf->buckets_arg; i++) { + if (pcd->peaks[i] > peak) { + peak = pcd->peaks[i]; + pos = 0; + } + } + /* determine target gain */ + gn = (1 << GAINSHIFT) * pcd->conf->target_level_arg / peak; + if (gn < (1 << GAINSHIFT)) + gn = 1 << GAINSHIFT; + pcd->target_gain = (pcd->target_gain * ((1 << pcd->conf->gain_smooth_arg) - 1) + gn) + >> pcd->conf->gain_smooth_arg; + /* give it an extra insignificant nudge to counteract possible + * rounding error + */ + if (gn < pcd->target_gain) + pcd->target_gain--; + else if (gn > pcd->target_gain) + pcd->target_gain++; + if (pcd->target_gain > pcd->conf->gain_max_arg << GAINSHIFT) + pcd->target_gain = pcd->conf->gain_max_arg << GAINSHIFT; + /* see if a peak is going to clip */ + gn = (1 << GAINSHIFT) * 32768 / peak; + if (gn < pcd->target_gain) { + pcd->target_gain = gn; + if (pcd->conf->anticlip_given) + pos = 0; + } else + /* we're ramping up, so draw it out over the whole frame */ + pos = length; + /* determine gain rate necessary to make target */ + if (!pos) + pos = 1; + gr = ((pcd->target_gain - pcd->current_gain) << 16) / pos; + gf = pcd->current_gain << 16; + ip = audio; + op = (int16_t *)(fn->buf + fn->loaded); + for (i = 0; i < length / 2; i++) { + int sample; + /* interpolate the gain */ + pcd->current_gain = gf >> 16; + if (i < pos) + gf += gr; + else if (i == pos) + gf = pcd->target_gain << 16; + /* amplify */ + sample = (*ip++) * pcd->current_gain * pcd->conf->volume_arg / 10 >> GAINSHIFT; + if (sample < -32768) { + pcd->clip++; + sample = -32768; + } else if (sample > 32767) { + pcd->clip++; + sample = 32767; + } + *op++ = sample; + } + pcd->pn = (pcd->pn + 1) % pcd->conf->buckets_arg; + PARA_DEBUG_LOG("bucket: %03i, input len: %i, length: %i, peak: %05i, " + "current gain: %03i, clipped: %d\n", pcd->pn, inbuf_len, + length, peak, pcd->current_gain, pcd->clip); + fn->loaded = length; + return length; +} + +static void close_compress(struct filter_node *fn) +{ + struct private_compress_data *pcd = fn->private_data; + free(pcd->peaks); + free(fn->private_data); + free(fn->buf); +} + +static void *compress_parse_config(int argc, char **argv) +{ + struct gengetopt_args_info *ret = para_calloc(sizeof(struct gengetopt_args_info)); + if (!compress_cmdline_parser(argc, argv, ret)) + return ret; + free(ret); + return NULL; +} + +static void open_compress(struct filter_node *fn) +{ + struct private_compress_data *pcd = para_calloc( + sizeof(struct private_compress_data)); +// compress_cmdline_parser(fn->argc, fn->argv, &pcd->conf); + pcd->conf = fn->conf; + pcd->peaks = para_calloc(pcd->conf->buckets_arg * sizeof(int)); + fn->private_data = pcd; + fn->bufsize = COMPRESS_CHUNK_SIZE; + fn->buf = para_malloc(fn->bufsize); + fn->loaded = 0; +} + +/** the init function of the compress filter */ +void compress_init(struct filter *f) +{ + f->open = open_compress; + f->close = close_compress; + f->convert = compress; + f->print_help = compress_cmdline_parser_print_help; + f->parse_config = compress_parse_config; +} diff --git a/compress_filter.ggo b/compress_filter.ggo new file mode 100644 index 00000000..6a6d7630 --- /dev/null +++ b/compress_filter.ggo @@ -0,0 +1,7 @@ +section "The dynamic audio range compressor" +option "anticlip" c "enable clipping protection" flag on +option "buckets" b "history length" int typestr="number" default="400" no +option "target_level" t "target signal level (1-32767)" int typestr="number" default="25000" no +option "gain_max" g "maximum amount to amplify by" int typestr="number" default="4" no +option "gain_smooth" i "how much inertia ramping has" int typestr="number" default="5" no +option "volume" v "set soft volume (0-10)" int typestr="number" default="10" no diff --git a/configure.ac b/configure.ac new file mode 100644 index 00000000..da353502 --- /dev/null +++ b/configure.ac @@ -0,0 +1,312 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ(2.53) + + +AC_INIT(paraslash, [cvs], maan@systemlinux.org) +AC_CONFIG_HEADER([config.h]) + +########################################################################### generic +AC_PROG_CC +AC_PROG_CPP +AC_PROG_INSTALL + +AC_HEADER_DIRENT +AC_HEADER_STDC +AC_HEADER_SYS_WAIT +AC_CHECK_HEADERS([arpa/inet.h ctype.h fcntl.h limits.h netdb.h netinet/in.h stdlib.h \ + string.h sys/socket.h sys/time.h sys/timeb.h sys/un.h sys/ipc.h unistd.h utime.h malloc.h], \ + [], [AC_MSG_ERROR([$ac_header not found])]) + +AC_CHECK_HEADER(linux/soundcard.h, [extras="$extras para_fade"], + [AC_MSG_WARN([linux/soundcard.h not found, \ + cannot build para_fade])]) + +# Checks for typedefs, structures, and compiler characteristics. +AC_C_CONST +AC_TYPE_OFF_T +AC_TYPE_PID_T +AC_TYPE_SIZE_T +AC_HEADER_TIME +AC_STRUCT_TM + +# Checks for library functions. +AC_FUNC_FORK +AC_PROG_GCC_TRADITIONAL +AC_FUNC_MALLOC +AC_FUNC_MEMCMP +AC_FUNC_MKTIME +AC_FUNC_MMAP +AC_FUNC_REALLOC +AC_FUNC_SELECT_ARGTYPES +AC_TYPE_SIGNAL +AC_FUNC_STAT +AC_FUNC_STRFTIME +AC_FUNC_VPRINTF +AC_CHECK_FUNCS([atexit dup2 gethostbyname inet_ntoa memchr memmove memset \ + regcomp select socket strchr strdup strerror strstr strtol uname], [], + [AC_MSG_ERROR([function not found, cannot live without it])]) +########################################################################### curses +AC_CHECK_LIB([ncurses], [initscr], [], + [AC_MSG_ERROR([libncurses not found])]) + +AC_CHECK_LIB([readline], [readline], [], + [AC_MSG_ERROR([libreadline not found])]) +AC_CHECK_LIB([menu], [new_menu], [extras="$extras para_dbadm"], + [AC_MSG_WARN([libmenu not found, cannot build para_dbadm])]) + +########################################################################### +recv_cmdline_objs="recv.cmdline http_recv.cmdline" +recv_errlist_objs="http_recv recv_common recv time string net" +recv_ldflags="" + +filter_cmdline_objs="filter.cmdline compress_filter.cmdline" +filter_errlist_objs="filter_chain wav compress filter string" +filter_ldflags="" + +audiod_cmdline_objs="audiod.cmdline grab_client.cmdline compress_filter.cmdline + http_recv.cmdline" +audiod_errlist_objs="audiod exec close_on_fork signal string daemon stat net + time grab_client filter_chain wav compress http_recv recv_common ringbuffer" +audiod_ldflags="" + +server_cmdline_objs="server.cmdline" +server_errlist_objs="server mp3 afs command net string signal dopey time daemon stat + crypt http_send db close_on_fork" +server_ldflags="" + +########################################################################### ssl +dnl @synopsis CHECK_SSL +dnl +dnl based on the follwing macro from the autoconf archive +dnl +dnl @category InstalledPackages +dnl @author Mark Ethan Trostler +dnl @version 2003-01-28 +dnl @license AllPermissive + +AC_DEFUN([CHECK_SSL], +[ + for ssldir in $1 /usr/local/ssl /usr/lib/ssl /usr/ssl /usr/pkg /usr/local /usr; do + AC_MSG_CHECKING(for openssl in $ssldir) + if test -f "$ssldir/include/openssl/ssl.h"; then + found_ssl="yes" + AC_MSG_RESULT(yes) + SSL_CFLAGS="-I$ssldir/include/openssl" + SSL_CPPFLAGS="-I$ssldir/include/openssl" + break + fi + if test -f "$ssldir/include/ssl.h"; then + found_ssl="yes"; + AC_MSG_RESULT(yes) + SSL_CFLAGS="-I$ssldir/include/"; + SSL_CPPFLAGS="-I$ssldir/include/"; + break + fi + AC_MSG_RESULT(no) + done + if test x_$found_ssl != x_yes; then + AC_MSG_ERROR(Cannot find ssl libraries) + else + SSL_LIBS="-lssl -lcrypto"; + SSL_LDFLAGS="-L$ssldir/lib"; + fi + AC_SUBST(SSL_CPPFLAGS) + AC_SUBST(SSL_CFLAGS) + AC_SUBST(SSL_LIBS) + AC_SUBST(SSL_LDFLAGS) +])dnl + +AC_ARG_ENABLE(ssldir, [AS_HELP_STRING(--enable-ssldir=path, + [Search for openssl also in path.])]) +if test "$enable_ssldir" = "yes"; then enable_ssldir=""; fi +CHECK_SSL($enable_ssldir) +server_ldflags="$srver_ldflags $SSL_LDFLAGS $SSL_LIBS" + +########################################################################### gtk2 + +pkg_modules="gtk+-2.0 >= 2.0.0" +PKG_CHECK_MODULES(GTK, [$pkg_modules], [extras="$extras para_krell.so"], [ + AC_MSG_WARN([gtk+-2 not found, can not build para_krell]) +]) +AC_SUBST(GTK_CFLAGS) +AC_SUBST(GTK_LIBS) + +########################################################################### sdl +AC_CHECK_LIB([SDL_image], [SDL_Init], [extras="$extras para_sdl_gui"], [ + AC_MSG_WARN([libSDL_image not found, cannot build para_sdl_gui]) +]) +AC_CHECK_HEADER(SDL/SDL.h, [], + [AC_MSG_WARN([SDL/SDL.h not found])]) + +########################################################################### mysql +have_mysql="yes" +AC_CHECK_HEADER(mysql/mysql.h, [], [ + have_mysql="no" +]) +AC_CHECK_LIB([mysqlclient], [mysql_init], [], [ + have_mysql="no" +]) +if test "$have_mysql" = "yes"; then + server_ldflags="$server_ldflags -lmysqlclient" + server_errlist_objs="$server_errlist_objs mysql" + AC_DEFINE(HAVE_MYSQL, 1, [define to 1 to turn on mysql support]) +else + AC_MSG_WARN([no libmysqlclient, cannot build mysql-based dbtool]) +fi +########################################################################### ogg +have_ogg="yes" +AC_CHECK_LIB([ogg], [ogg_stream_init], [], [ + have_ogg="no" +]) +AC_CHECK_LIB([vorbis], [vorbis_info_init], [], [ + have_ogg="no" +]) +AC_CHECK_HEADERS([ogg/ogg.h vorbis/codec.h], [], [ + have_ogg="no" +]) +if test "$have_ogg" = "yes"; then + AC_DEFINE(HAVE_OGGVORBIS, 1, define to 1 to turn on ogg vorbis support) + server_ldflags="$server_ldflags -logg -lvorbis -lvorbisfile" + filter_ldflags="$filter_ldflags -lvorbis -lvorbisfile" + audiod_ldflags="$audiod_ldflags -lvorbis -lvorbisfile" + + filter_cmdline_objs="$filter_cmdline_objs oggdec_filter.cmdline" + audiod_cmdline_objs="$audiod_cmdline_objs oggdec_filter.cmdline" + + server_errlist_objs="$server_errlist_objs ogg" + filter_errlist_objs="$filter_errlist_objs oggdec" + audiod_errlist_objs="$audiod_errlist_objs oggdec" +else + AC_MSG_WARN([no ogg vorbis support in para_server/para_filter]) +fi +########################################################################### mad +have_mad="yes" +AC_CHECK_HEADERS([mad.h], [], [ + have_mad="no" +]) +AC_CHECK_LIB([mad], [mad_stream_init], [], [ + have_mad="no" +]) +if test "$have_mad" = "yes"; then + AC_DEFINE(HAVE_MAD, 1, define to 1 if you want to build the mp3dec filter) + filter_errlist_objs="$filter_errlist_objs mp3dec" + audiod_errlist_objs="$audiod_errlist_objs mp3dec" + filter_ldflags="$filter_ldflags -lmad" + audiod_ldflags="$audiod_ldflags -lmad" +else + AC_MSG_WARN([no mp3dec support in para_audiod/para_filter]) +fi +########################################################################### alsa +play="para_play" +msg="will not build para_play" +AC_CHECK_HEADERS([alsa/asoundlib.h], [], [ + AC_MSG_WARN([no alsa/asoundlib, $msg]) + play="" +]) +AC_CHECK_LIB([asound], [snd_pcm_open], [], [ + AC_MSG_WARN([no libasound, $msg]) + play="" +]) +extras="$extras $play" + + +########################################################################### ortp +have_ortp="yes" +pkg_modules="glib-2.0 >= 2.0.4" +PKG_CHECK_MODULES(GLIB, [$pkg_modules], [], [ + have_ortp="no" +]) +CPPFLAGS="$CPPFLAGS $GLIB_CFLAGS" +AC_CHECK_HEADERS([ortp/ortp.h], [], [ + have_ortp="no" +]) +AC_CHECK_LIB([ortp], [ortp_init], [], [ + have_ortp="no" +]) +if test "$have_ortp" = "yes"; then + recv_cmdline_objs="$recv_cmdline_objs ortp_recv.cmdline" + recv_errlist_objs="$recv_errlist_objs ortp_recv" + + audiod_cmdline_objs="$audiod_cmdline_objs ortp_recv.cmdline" + audiod_errlist_objs="$audiod_errlist_objs ortp_recv" + + server_errlist_objs="$server_errlist_objs ortp_send" + + recv_ldflags="$recv_ldflags $GLIB_LIBS -lortp" + server_ldflags="$server_ldflags $GLIB_LIBS -lortp" + audiod_ldflags="$audiod_ldflags $GLIB_LIBS -lortp" + AC_DEFINE(HAVE_ORTP, 1, [define to 1 to turn on ortp support]) + +else + AC_MSG_WARN([deactivating ortp support]) +fi +AC_SUBST(GLIB_CFLAGS) +AC_SUBST(GLIB_LIBS) + +########################################################################### zmw +slide="para_slider" +msg="can not build para_slider" +CPPFLAGS="$GTK_CFLAGS" +LDFLAGS="$LDFLAGS $GTK_LIBS" +AC_CHECK_HEADERS([zmw/zmw.h], [], [ + AC_MSG_WARN([zero memory widget header files not found, $msg]) + slide="" +]) +AC_CHECK_LIB([zmw], [zmw_init], [], [ + AC_MSG_WARN([zero memory widget library not found, $msg]) + slide="" +]) +extras="$extras $slide" + + + + + +AC_SUBST(extra_binaries, [$extras]) +AC_SUBST(extra_filter_objs, [$extra_filter_objs]) +AC_SUBST(extra_filter_libs, [$extra_filter_libs]) +AC_SUBST(install_sh, [$INSTALL]) +AC_CONFIG_FILES([Makefile]) + + + +AC_DEFUN([add_dot_o],[$(for i in $@; do printf "$i.o "; done)]) +AC_DEFUN([objlist_to_errlist],[$(for i in $@; do printf "DEFINE_ERRLIST($(echo $i| tr 'a-z' 'A-Z'));"; done) [const char **para_errlist[[]]] = {$(for i in $@; do printf "PARA_ERRLIST($(echo $i | tr 'a-z' 'A-Z')), "; done) }]) + +recv_objs="$recv_cmdline_objs $recv_errlist_objs" +filter_objs="$filter_cmdline_objs $filter_errlist_objs" +audiod_objs="$audiod_cmdline_objs $audiod_errlist_objs" +server_objs="$server_cmdline_objs $server_errlist_objs" + +AC_SUBST(recv_objs, add_dot_o($recv_objs)) +AC_SUBST(recv_ldflags, $recv_ldflags) +AC_DEFINE_UNQUOTED(INIT_RECV_ERRLISTS, objlist_to_errlist($recv_errlist_objs), + errors used by para_recv) + +AC_SUBST(filter_objs, add_dot_o($filter_objs)) +AC_SUBST(filter_ldflags, $filter_ldflags) +AC_DEFINE_UNQUOTED(INIT_FILTER_ERRLISTS, + objlist_to_errlist($filter_errlist_objs), errors used by para_filter) + +AC_SUBST(audiod_objs, add_dot_o($audiod_objs)) +AC_SUBST(audiod_ldflags, $audiod_ldflags) +AC_DEFINE_UNQUOTED(INIT_AUDIOD_ERRLISTS, objlist_to_errlist($audiod_errlist_objs), + errors used by para_audiod) + +AC_SUBST(server_objs, add_dot_o($server_objs)) +AC_SUBST(server_ldflags, $server_ldflags) +AC_DEFINE_UNQUOTED(INIT_SERVER_ERRLISTS, + objlist_to_errlist($server_errlist_objs), errors used by para_server) +AC_OUTPUT +AC_MSG_NOTICE([creating Makefile.deps]) +gcc -MM -MG *.c > Makefile.deps +AC_MSG_NOTICE([ +paraslash configuration: +~~~~~~~~~~~~~~~~~~~~~~~~ +mysql support: $have_mysql +ogg vorbis support: $have_ogg +mp3dec support (libmad): $have_mad +ortp support: $have_ortp +]) diff --git a/crypt.c b/crypt.c new file mode 100644 index 00000000..34407d78 --- /dev/null +++ b/crypt.c @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2005-2006 Andre Noll + * + * 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 +#include "para.h" +#include "error.h" +#include "string.h" + +/** \cond used to distinguish between loading of private/public key */ +#define LOAD_PUBLIC_KEY 0 +#define LOAD_PRIVATE_KEY 1 +/** \endcond **/ + +static EVP_PKEY *load_key(const char *file, int private) +{ + BIO *key; + EVP_PKEY *pkey = NULL; + + key = BIO_new(BIO_s_file()); + if (!key) + return NULL; + if (BIO_read_filename(key, file) > 0) { + if (private == LOAD_PRIVATE_KEY) + pkey = PEM_read_bio_PrivateKey(key, NULL, NULL, NULL); + else + pkey = PEM_read_bio_PUBKEY(key, NULL, NULL, NULL); + } + BIO_free(key); + return pkey; +} + +static int get_key(char *key_file, RSA **rsa, int private) +{ + EVP_PKEY *key = load_key(key_file, private); + + if (!key) + return (private == LOAD_PRIVATE_KEY)? -E_PRIVATE_KEY : -E_PUBLIC_KEY; + *rsa = EVP_PKEY_get1_RSA(key); + EVP_PKEY_free(key); + if (!*rsa) + return -E_RSA; + return RSA_size(*rsa); +} + +/** + * decrypt a buffer using an RSA key + * + * \param key_file full path of the rsa key + * \param outbuf the output buffer + * \param inbuf the encrypted input buffer + * \param rsa_inlen the length of \a inbuf + * + * The \a outbuf must be large enough to hold at least \a rsa_inlen bytes. + * + * \return The size of the recovered plaintext on success, negative on errors. + * + * \sa RSA_private_decrypt(3) + **/ +int para_decrypt_buffer(char *key_file, unsigned char *outbuf, unsigned char *inbuf, + int rsa_inlen) +{ + RSA *rsa; + int ret = get_key(key_file, &rsa, LOAD_PRIVATE_KEY); + + if (ret < 0) + return ret; + ret = RSA_private_decrypt(rsa_inlen, inbuf, outbuf, rsa, RSA_PKCS1_PADDING); + return (ret > 0)? ret : -E_DECRYPT; +} + +/** + * decrypt the challenge number sent by para_server + * + * \param key_file full path of the rsa key + * \param challenge_nr result is stored here + * \param inbuf the input buffer + * \param rsa_inlen the length of \a inbuf + * + * \return positive on success, negative on errors + * + * \sa para_decrypt_buffer() + */ +int para_decrypt_challenge(char *key_file, long unsigned *challenge_nr, + unsigned char *inbuf, int rsa_inlen) +{ + unsigned char *rsa_out = OPENSSL_malloc(128); + int ret = para_decrypt_buffer(key_file, rsa_out, inbuf, rsa_inlen); + + if (ret >= 0) + ret = sscanf((char *)rsa_out, "%lu", challenge_nr) == 1? + 1 : -E_CHALLENGE; + OPENSSL_free(rsa_out); + return ret; +} + +/** + * encrypt a buffer using an RSA key + * + * \param key_file full path of the rsa key + * \param inbuf the input buffer + * \param len the length of \a inbuf + * \param outbuf the output buffer + * + * \return The size of the encrypted data on success, negative on errors + * + * \sa RSA_public_encrypt(3) + */ +int para_encrypt_buffer(char *key_file, unsigned char *inbuf, + const unsigned len, unsigned char *outbuf) +{ + RSA *rsa; + int ret = get_key(key_file, &rsa, LOAD_PUBLIC_KEY); + + if (ret < 0) + return ret; + ret = RSA_public_encrypt(len, inbuf, outbuf, rsa, RSA_PKCS1_PADDING); + return ret < 0? -E_ENCRYPT : ret; +} + +/** + * encrypt the given challenge number + * + * \param key_file full path of the rsa key + * \param challenge_nr the number to be encrypted + * \param outbuf the output buffer + * + * \a outbuf must be at least 64 bytes long + * + * \return The size of the encrypted data on success, negative on errors + * + * \sa para_encrypt_buffer() + * + */ +int para_encrypt_challenge(char *key_file, long unsigned challenge_nr, + unsigned char *outbuf) +{ + unsigned char *inbuf = (unsigned char*) make_message("%lu", challenge_nr); + int ret = para_encrypt_buffer(key_file, inbuf, strlen((char *)inbuf), outbuf); + free(inbuf); + return ret; +} + diff --git a/crypt.h b/crypt.h new file mode 100644 index 00000000..82ed4f9d --- /dev/null +++ b/crypt.h @@ -0,0 +1,9 @@ +/** \file crypt.h prototypes for the RSA crypt functions */ +int para_decrypt_challenge(char *key_file, long unsigned *challenge_nr, + unsigned char *buf, int rsa_inlen); +int para_encrypt_challenge(char *key_file, long unsigned challenge_nr, + unsigned char *outbuf); +int para_encrypt_buffer(char *key_file, unsigned char *inbuf, const unsigned len, + unsigned char *outbuf); +int para_decrypt_buffer(char *key_file, unsigned char *outbuf, unsigned char *inbuf, + int rsa_inlen); diff --git a/daemon.c b/daemon.c new file mode 100644 index 00000000..616fd2c2 --- /dev/null +++ b/daemon.c @@ -0,0 +1,173 @@ +/* + * Copyright (C) 1997-2006 Andre Noll + * + * 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 +#include "string.h" + +/** + * do the usual stuff to become a daemon + * + * Fork, become session leader, dup fd 0, 1, 2 to /dev/null. + * + * \sa fork(2), setsid(2), dup(2) + */ +void daemon_init(void) +{ + pid_t pid; + int null; + + PARA_INFO_LOG("%s", "daemonizing\n"); + if ((pid = fork()) < 0) { + PARA_EMERG_LOG("%s", "failed to init daemon\n"); + exit(EXIT_FAILURE); + } else if (pid) + exit(EXIT_SUCCESS); /* parent exits */ + /* child */ + setsid(); /* become session leader */ + chdir("/"); + umask(0); + + null = open("/dev/null", O_RDONLY); + if (null < 0) + exit(EXIT_FAILURE); + dup2(null, STDIN_FILENO); + dup2(null, STDOUT_FILENO); + dup2(null, STDERR_FILENO); + close(null); +} + +/** + * fopen() a file in append mode + * + * \param logfile_name the name of the file to open + * + * Either calls exit() or returns a valid file handle. + */ +/* might be called from para_log, so we must not use para_log */ +FILE *open_log(const char *logfile_name) +{ + FILE *logfile; + + if (!logfile_name) + return NULL; + if (!(logfile = fopen(logfile_name, "a"))) + exit(EXIT_FAILURE); + setlinebuf(logfile); + return logfile; +} + +/** + * close the log file of the daemon + * + * \param logfile the log file handle + * + * It's OK to call this with logfile == NULL + */ +void close_log(FILE* logfile) +{ + if (!logfile) + return; + PARA_INFO_LOG("%s", "closing logfile\n"); + fclose(logfile); +} + +/** + * log the startup message containing the paraslash version + */ +void log_welcome(const char *whoami, int loglevel) +{ + PARA_INFO_LOG("welcome to %s " VERSION " ("BUILD_DATE")\n", whoami); + PARA_DEBUG_LOG("using loglevel %d\n", loglevel); +} + +/** + * give up superuser privileges + * + * This function returns immediately if not invoked with EUID zero. Otherwise, + * it tries to obtain the UID for the user specified by \a username and exits + * if this user was not found. On success, effective and real UID and the saved + * set-user-ID are all set to the UID of \a username. + * + * \sa getpwnam(3), getuid(2), setuid(2) + */ +void para_drop_privileges(const char *username) +{ + struct passwd *p; + char *tmp; + + if (geteuid()) + return; + if (!username) { + PARA_EMERG_LOG("%s", "root privileges, but no user option given\n"); + exit(EXIT_FAILURE); + } + tmp = para_strdup(username); + p = getpwnam(tmp); + free(tmp); + if (!p) { + PARA_EMERG_LOG("%s", "no such user\n"); + exit(EXIT_FAILURE); + } + PARA_NOTICE_LOG("%s", "dropping root privileges\n"); + setuid(p->pw_uid); + PARA_DEBUG_LOG("uid: %d, euid: %d\n", getuid(), geteuid()); + setuid(p->pw_uid); +} + +/** + * set/get the server uptime + * + * \param set_or_get chose one of the two modes + * + * This should be called at startup time with \a set_or_get equal to \p + * UPTIME_SET which sets the uptime to zero. Subsequent calls with \a + * set_or_get equal to \p UPTIME_GET return the number of seconds ellapsed + * since the last reset. + * + * \sa time(2), difftime(3) + */ +time_t server_uptime(enum uptime set_or_get) +{ + static time_t startuptime; + time_t now; + + if (set_or_get == UPTIME_SET) { + time(&startuptime); + return 0; + } + time(&now); + return (time_t) difftime(now, startuptime); +} + +/** + * construct string containing uptime + * + * The format of the returned string is "days:hours:minutes" + * + * \sa server_uptime + */ +__malloc char *uptime_str(void) +{ + time_t t = server_uptime(UPTIME_GET); + return make_message("%li:%02li:%02li", t / 86400, + (t / 3600) % 24, (t / 60) % 60); +} + diff --git a/daemon.h b/daemon.h new file mode 100644 index 00000000..11035b50 --- /dev/null +++ b/daemon.h @@ -0,0 +1,12 @@ +/** \file daemon.h exported symbols from daemon.c */ + + +void daemon_init(void); +FILE *open_log(const char *logfile_name); +void close_log(FILE* logfile); +void log_welcome(const char *whoami, int loglevel); +void para_drop_privileges(const char *username); +/** used for server_uptime() */ +enum uptime {UPTIME_SET, UPTIME_GET}; +time_t server_uptime(enum uptime set_or_get); +__malloc char *uptime_str(void); diff --git a/db.c b/db.c new file mode 100644 index 00000000..d97a6e0e --- /dev/null +++ b/db.c @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2005-2006 Andre Noll + * + * 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 /* readdir() */ +#include /* stat */ +#include /* mode_t */ +#include "error.h" +#include "string.h" + +/* + * return 1 if name matches any supported audio format + */ +static int match_audio_file_name(char *name) +{ + int i, len = strlen(name); + const char *pattern[] = {SUPPORTED_AUDIO_FORMATS_ARRAY}; + + for (i = 0; pattern[i]; i++) { + const char *p = pattern[i]; + int plen = strlen(p); + if (len < plen + 1) + continue; + if (name[len - plen - 1] != '.') + continue; + if (strcasecmp(name + len - plen, p)) + continue; + return 1; + } + return 0; +} + +/** + * traverse the given directory recursively + * + * @param dirname the directory to traverse + * @param f: the function to call for each entry. + * + * for each regular file whose filename ends in .yyy, where yyy is a supported + * audio format, the supplied function \a f is called. The directory and + * filename component of the regular file are passed to \a f. + * + * \return On success, 1 is returned. Otherwise, this function returns a + * negative value which indicates the kind of the error. + */ +int find_audio_files(const char *dirname, int (*f)(const char *, const char *)) +{ + DIR *dir = NULL; + struct dirent *entry; + /* + * Opening the current directory (".") and calling fchdir() to return + * is usually faster and more reliable than saving cwd in some buffer + * and calling chdir() afterwards (see man 3 getcwd). + */ + char cwd_fd = open(".", O_RDONLY); + struct stat s; + int ret = -1; + +// PARA_DEBUG_LOG("dirname: %s\n", dirname); + if (cwd_fd < 0) + return -E_GETCWD; + ret = -E_CHDIR; + if (chdir(dirname) < 0) + goto out; + ret = -E_OPENDIR; + dir = opendir("."); + if (!dir) + goto out; + /* scan cwd recursively */ + while ((entry = readdir(dir))) { + mode_t m; + char *tmp; + + if (!strcmp(entry->d_name, ".")) + continue; + if (!strcmp(entry->d_name, "..")) + continue; + ret = -E_LSTAT; + if (lstat(entry->d_name, &s) == -1) + goto out; + m = s.st_mode; + if (!S_ISREG(m) && !S_ISDIR(m)) /* skip links, sockets, ... */ + continue; + if (S_ISREG(m)) { /* regular file */ + if (!match_audio_file_name(entry->d_name)) + continue; + if (f(dirname, entry->d_name) < 0) + goto out; + continue; + } + /* directory */ + tmp = make_message("%s/%s", dirname, entry->d_name); + ret = find_audio_files(tmp, f); + free(tmp); + if (ret < 0) + goto out; + } + ret = 1; +out: + if (dir) + closedir(dir); + if (fchdir(cwd_fd) < 0) + ret = -E_CHDIR; + close(cwd_fd); + if (ret < 0) + PARA_ERROR_LOG("ret = %x\n", -ret); + return ret; +} diff --git a/db.h b/db.h new file mode 100644 index 00000000..960bf59f --- /dev/null +++ b/db.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2005-2006 Andre Noll + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ + +/** \file db.h data structures common to all database tools */ + +int find_audio_files(const char *dirname, int (*f)(const char *, const char *)); + +/** + * describes one of para_server's supported database tools + * + * There is exactly one such struct for each supported database tool. During + * the startup part of para_server the \a init() function of the activated + * database tool gets called which fills in the other members. + + * + * + */ +struct dbtool { +/** + * name name of this database tool + */ +const char *name; +/** + * the database init routine + * + * It should check its command line options and do all necessary initialization + * like connecting to a database server. + * + * A negative return value indicates an initialization error and means that + * this database tool should be ignored for now (it may later be activated + * again via the cdt command). + * + * If \a init() returns success (non-negative return value), it must have + * initialized in all non-optional function pointers of the given dbtool + * struct. Moreover, \a cmd_list must point to a NULL-terminated array which + * holds the list of all commands that are supported by this database tool. + */ +int (*init)(struct dbtool *self); +/** + * list of commands supported by this dbtool + */ +struct server_command *cmd_list; +/** + * pointer to function returning list of at most \a num audio files to be + * streamed next + * + * \a get_audio_file_list() must return a pointer to a array of at most \a num + * char* pointers (terminated by a NULL pointer), or NULL on errors. Both the + * array and its contents must be dynamically allocated and are freed by the + * caller. + * +*/ +char **(*get_audio_file_list)(unsigned int num); +/** + * + * the update hook + * + * The \a update_audio_file pointer is optional and need not be supplied. In this + * case it is not neccessary to init this pointer from within init(). If + * \a update_audio_file is non-NULL, the function it points to gets called + * whenever a new audio file was successfully loaded and is going to be + * streamed by any of paraslash's senders. The full path of the audio file is + * passed \a update_audio_file(). + * + */ +void (*update_audio_file)(char *audio_file); +/** + * + * shutdown this database tool and free all resources + * + * This gets called whenever the database tool changes (via the cdt command), + * or when para_server receives the HUP signal, or when para_server shuts down. + * It is assumed to succeed. +*/ +void (*shutdown)(void); +}; + +int mysql_dbtool_init(struct dbtool*); +int dopey_dbtool_init(struct dbtool*); + diff --git a/dbadm.c b/dbadm.c new file mode 100644 index 00000000..5f807eb2 --- /dev/null +++ b/dbadm.c @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2004-2006 Andre Noll + * + * 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 +#include "string.h" + +//#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) +#define MAXLINE 255 + +#define OFFSET 4 +static int att_win_lines, att_win_cols; +static int att_format_lines, att_format_cols; +static int atts_modified, refresh_file = 1; +static int n_choices, choice_len; +static char *atts; +ITEM **my_items; +WINDOW *att_win; + +static char **choices; + +/* no looging */ +void para_log(__unused int ll, __unused char *fmt,...) +{ +} + +static int client_cmd(const char *cmd) +{ + pid_t pid; + int ret, fds[3] = {0, 1, 0}; + char *cmdline = make_message(BINDIR "/para_client %s", cmd); + ret = para_exec_cmdline_pid(&pid, cmdline, fds); + free(cmdline); + if (ret < 0) + return -1; + return fds[1]; +} + +static char **get_all_atts(int *num_atts) +{ + int fd = client_cmd("laa"); + FILE *pipe; + char **ret = NULL, *buf; + + if (fd < 0) + return NULL; + pipe = fdopen(fd, "r"); + if (!pipe) { + close(fd); + return NULL; + } + *num_atts = 0; + buf = para_malloc(MAXLINE * sizeof(char)); + while (fgets(buf, MAXLINE - 1, pipe) && *buf) { + size_t n = strlen(buf); + buf[n - 1] = '\0'; + if (choice_len < n - 1) + choice_len = n - 1; + ret = para_realloc(ret, (*num_atts + 1) * sizeof(char*)); + ret[*num_atts] = para_strdup(buf); + *num_atts += 1; + } + free(buf); + return ret; +} + +static char *get_atts(char *filename) +{ + int n, fd, bufsize = (n_choices * (choice_len + 1) + 10) * sizeof(char); + char *cmd = make_message("la %s", filename), *buf; + + fd = client_cmd(cmd); + free(cmd); + if (fd < 0) + return NULL; + buf = para_malloc(bufsize * sizeof(char)); + n = read(fd, buf, bufsize - 1); + if (n <= 0 ||strstr(buf, "Not contained in database")) { + free(buf); + return NULL; + }; + return buf; +} + +static void _item_init(__unused MENU* menu) +{ +// static int subsequent_run; + int i, n; + char *p = atts; + char att[MAXLINE]; + + if (!refresh_file) + return; + refresh_file = 0; + for (i = 0; i < n_choices; i++) + set_item_value(my_items[i], FALSE); + while (sscanf(p, "%s%n", att, &n) > 0) { + //mvprintw(LINES - 4, 0, "aaaitem."); + p += n + 1; + for (i = 0; i < n_choices; i++) { + if (!strcmp(item_name(my_items[i]), att)) { + set_item_value(my_items[i], TRUE); + break; + } + } + } +} + +struct color_pair { + int bg; + int fg; +}; + +enum { + COLOR_DUMMY, + COLOR_FRAME, + COLOR_FILENAME, + COLOR_ACTIVE_ITEM, + COLOR_INACTIVE_ITEM +}; + +static void init_colors(void) +{ + start_color(); + init_pair(COLOR_FRAME, COLOR_BLUE, COLOR_BLACK); + init_pair(COLOR_FILENAME, COLOR_MAGENTA, COLOR_BLACK); + init_pair(COLOR_ACTIVE_ITEM, COLOR_RED, COLOR_WHITE); + init_pair(COLOR_INACTIVE_ITEM, COLOR_WHITE, COLOR_BLACK); +} + +static int commit_changes(char *filename) +{ +// ITEM **items; + int i; + char buf[MAXLINE] = "para_client sa "; + + for (i = 0; i < n_choices; ++i) { + strcat(buf, item_name(my_items[i])); + if (item_value(my_items[i])) { + // printf("%s\n", item_name(my_items[i])); + strcat(buf, "+ "); + } else + strcat(buf, "- "); + } + strcat(buf, filename); + //printf("old atts: %s\n", atts); + //printf("%s\n", buf); + return system(buf); +} + +static char *get_current_filename(void) +{ + char *bn = NULL, *buf = para_malloc(MAXLINE * sizeof(char)); + int ret, fd; + + fd = client_cmd("sc 1"); + if (fd < 0) + return NULL; + ret = read(fd, buf, MAXLINE - 1); + if (ret <= 0) + goto out; + buf[ret] = '\0'; + bn = para_basename(buf); + free(buf); +out: + close(fd); + return bn; +} + +static void print_filename(char *filename) +{ + char *tmp = strdup(filename); + int maxlen = att_win_cols - 2; + + wattron(att_win, COLOR_PAIR(COLOR_FILENAME)); + if (strlen(filename) > maxlen) + tmp[maxlen] = '\0'; + wmove(att_win, 1, 1); + clrtoeol(); + mvwprintw(att_win, 1, 1, "%s", tmp); + wattron(att_win, COLOR_PAIR(COLOR_FRAME)); + mvwaddch(att_win, 2, att_win_cols - 1, ACS_RTEE); + free(tmp); +} + +static int com_refresh_file(char *filename) { + + + filename = get_current_filename(); + if (!filename) + return -1; + atts = get_atts(filename); + if (!atts) + return -1; + print_filename(filename); + return 1; +} + +static int init_curses(void) +{ + /* Initialize curses */ + initscr(); + + if (LINES <= OFFSET + 4) + return -1; + + att_format_cols = (COLS - 2 * OFFSET - 3) / (choice_len + 1); + if (att_format_cols < 1) + return -1; + att_format_lines = (n_choices - 1) / att_format_cols + 1; + if (att_format_lines + OFFSET + 4 > LINES) + att_format_lines = LINES - OFFSET - 4; + + att_win_lines = att_format_lines + 4; + att_win_cols = (choice_len + 1) * att_format_cols + 1; + if (att_win_lines + OFFSET > LINES) + att_win_lines = LINES - OFFSET - 1; + if (att_win_cols + 2 * OFFSET > COLS) + att_win_cols = COLS - 2 * OFFSET + 1; + //printf ("%i:%i, %i:%i\n", att_format_lines, att_format_cols, att_win_lines, att_win_cols); fflush(stdout); sleep(2); + + cbreak(); + noecho(); + keypad(stdscr, TRUE); + init_colors(); + return 1; +} + +int main(int argc, char *argv[]) +{ + int c, i, ret = EXIT_FAILURE; + MENU *my_menu = NULL; + char *filename; + + choices = get_all_atts(&n_choices); + if (!choices || n_choices <= 0) + exit(EXIT_FAILURE); + if (argc < 2) { + filename = get_current_filename(); + if (!filename) + exit(EXIT_FAILURE); + } else + filename = strdup(argv[1]); + atts = get_atts(filename); + if (!atts) + goto out; + if (init_curses() < 0) + goto out; + + /* Initialize items */ + my_items = (ITEM **)calloc(n_choices + 1, sizeof(ITEM *)); + for (i = 0; i < n_choices; ++i) + my_items[i] = new_item(choices[i], ""); + + my_menu = new_menu(my_items); + set_item_init(my_menu, _item_init); + /* Make the menu multi valued */ + menu_opts_off(my_menu, O_ONEVALUE); + /* Set menu option not to show the description */ + menu_opts_off(my_menu, O_SHOWDESC); + + /* Create the window to be associated with the menu */ + att_win = newwin(att_win_lines, att_win_cols, OFFSET, OFFSET); + keypad(att_win, TRUE); + /* Set main window and sub window */ + set_menu_win(my_menu, att_win); + set_menu_sub(my_menu, derwin(att_win, att_win_lines - 4, + att_win_cols - 2, 3, 1)); + set_menu_format(my_menu, att_format_lines, att_format_cols); + //set_menu_format(my_menu, 5, 1); + set_menu_mark(my_menu, ""); + + /* Print a border around the main window and print a title */ + wattron(att_win, COLOR_PAIR(COLOR_FRAME)); + box(att_win, 0, 0); + mvwhline(att_win, 2, 1, ACS_HLINE , att_win_cols - 2); + mvwaddch(att_win, 2, 0, ACS_LTEE); + print_filename(filename); + set_menu_fore(my_menu, COLOR_PAIR(COLOR_ACTIVE_ITEM) | A_REVERSE); + set_menu_back(my_menu, COLOR_PAIR(COLOR_INACTIVE_ITEM)); + refresh(); + post_menu(my_menu); + +repeat: + wrefresh(att_win); + c = getch(); + switch(c) { + case KEY_DOWN: + menu_driver(my_menu, REQ_DOWN_ITEM); + goto repeat; + case KEY_UP: + menu_driver(my_menu, REQ_UP_ITEM); + goto repeat; + case KEY_LEFT: + menu_driver(my_menu, REQ_LEFT_ITEM); + goto repeat; + case KEY_RIGHT: + menu_driver(my_menu, REQ_RIGHT_ITEM); + goto repeat; + + case 'q': + ret = EXIT_SUCCESS; + goto out; + case 'r': + com_refresh_file(filename); + refresh_file = 1; + _item_init(NULL); + goto repeat; + + case ' ': + menu_driver(my_menu, REQ_TOGGLE_ITEM); + atts_modified = 1; + goto repeat; + /* Enter */ + case 10: + endwin(); + if (atts_modified) + commit_changes(filename); + else + printf("Attributes unchanged\n"); + goto out; + default: + goto repeat; + } +out: + if (my_items) { + free_item(my_items[0]); + free_item(my_items[1]); + } + for (i = 0; i < n_choices; i++) + free(choices[i]); + free(choices); + if (my_menu) + free_menu(my_menu); + if (atts) + free(atts); + if (filename) + free(filename); + endwin(); + exit(ret); +} + diff --git a/dopey.c b/dopey.c new file mode 100644 index 00000000..91e13685 --- /dev/null +++ b/dopey.c @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2004-2006 Andre Noll + * + * 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 /* gettimeofday */ +#include "server.cmdline.h" +#include "server.h" +#include "db.h" +#include "error.h" +#include "net.h" +#include "string.h" + +static int com_dopey(int, int, char **); +extern struct gengetopt_args_info conf; +extern struct misc_meta_data *mmd; + +static unsigned int num_audio_files, audio_file_count; +static char **audio_file_list; + +static int count_audio_files(__unused const char *dir, __unused const char *name) +{ + num_audio_files++; + return 1; +} + +static int remember_file(const char *dir, const char *name) +{ + if (audio_file_count >= num_audio_files) + return -E_FILE_COUNT; + audio_file_list[audio_file_count] = make_message("%s/%s", dir, name); + audio_file_count++; + return 1; +} + +/* array of commands that are supported by this database tool */ +static struct server_command cmds[] = { +{ +.name = "dopey", +.handler = com_dopey, +.perms = 0, +.description = "about the dopey database tool", +.synopsis = "dopey", +.help = + +"It's so dumb. It hurts. Don't use it; switch to the mysql database\n" +"tool instead. OTOH: You typed 'help dopey', so if you serious about\n" +"that and you really intend to help the dopey database tool, look at\n" +"my source code, dopey.c, and modify it to make it something useful.\n" + +}, { +.name = NULL, +} +}; + +static int com_dopey(int fd, __unused int argc, __unused char *argv[]) +{ + return send_buffer(fd, "Please do not use me. I'm too sick to do " + "anything for you. Switch me off. Now!\n"); +} + +/* + * Load a list of all audio files into memory and chose num of them randomly. + * Called by server to determine next audio file to be streamed. + */ +static char **dopey_get_audio_file_list(unsigned int num) +{ + int i, ret; + unsigned int len; + char **ret_list = NULL; /* what we are going to return */ + + audio_file_list = NULL; + num_audio_files = 0; + /* first run, just count all audio files. dopey */ + ret = find_audio_files(conf.dopey_dir_arg, count_audio_files); + if (ret < 0) + goto out; + ret = -E_NOTHING_FOUND; + if (!num_audio_files) + goto out; + /* yeah, that doesn't scale, also dopey */ + audio_file_list = para_malloc(num_audio_files * sizeof(char *)); + audio_file_count = 0; + /* second run (hot dentry cache, hopefully), fill audio_file_list */ + ret = find_audio_files(conf.dopey_dir_arg, remember_file); + if (ret < 0) + goto out; + /* careful, files might got deleted underneath */ + num_audio_files = audio_file_count; /* can only decrease */ + len = MIN(num, num_audio_files); + ret = -E_NOTHING_FOUND; + if (!len) /* nothing found, return NULL */ + goto out; + /* success, return NULL-terminated list */ + ret_list = para_calloc((len + 1) * sizeof(char *)); + for (i = 0; i < len; i++) { /* choose randomly */ + int r = (int) ((num_audio_files + 0.0) * (rand() + / (RAND_MAX + 1.0))); + ret_list[i] = para_strdup(audio_file_list[r]); + } +out: + if (audio_file_list) { + for (i = 0; i < num_audio_files; i++) + free(audio_file_list[i]); + free(audio_file_list); + } +// if (ret < 0) +// PARA_ERROR_LOG("%s\n", PARA_STRERROR(-ret)); + return ret_list; +} + +static void dopey_shutdown(void) +{ + PARA_DEBUG_LOG("%s", "thanks for using another dbtool.\n"); +} + +/** dopey's (constant) database info text */ +#define DBINFO "dbinfo1:database info? You're kidding. I'm dopey!\ndbinfo2:\ndbinfo3:\n" + +/** the dopey init function + * + * Init all function pointers of \a db, init the dbinfo text and seed the + * PRNG. + * + * \sa struct dbtool, misc_meta_data::dbinfo, mysql.c + */ +int dopey_dbtool_init(struct dbtool *db) +{ + struct timeval now; + + PARA_INFO_LOG("%s", "registering dopey handlers\n"); + sprintf(mmd->dbinfo, DBINFO); + gettimeofday(&now, NULL); + srand(now.tv_usec); + db->cmd_list = cmds; + db->get_audio_file_list = dopey_get_audio_file_list; + db->shutdown = dopey_shutdown; + return 1; +} diff --git a/error.h b/error.h new file mode 100644 index 00000000..bc8475e0 --- /dev/null +++ b/error.h @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2006 Andre Noll + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ + +/** \file error.h list of error messages for all subsystems */ + +/** \cond list of all subsystems that support the shiny new error facility */ +enum para_subsystem {SS_RECV, + SS_NET, SS_ORTP_RECV, SS_AUDIOD, SS_EXEC, SS_CLOSE_ON_FORK, SS_SIGNAL, + SS_STRING, SS_DAEMON, SS_STAT, SS_TIME, SS_GRAB_CLIENT, SS_HTTP_RECV, + SS_RECV_COMMON, SS_FILTER_CHAIN, SS_WAV, SS_COMPRESS, SS_OGGDEC, SS_FILTER, + SS_COMMAND, SS_DOPEY, SS_CRYPT, SS_HTTP_SEND, SS_ORTP_SEND, SS_DB, SS_OGG, + SS_MP3, SS_MP3DEC, SS_SERVER, SS_AFS, SS_MYSQL, SS_RINGBUFFER}; +#define NUM_SS (SS_RINGBUFFER + 1) +extern const char **para_errlist[]; +/** \endcond */ + +#define NET_ERRORS \ + PARA_ERROR(SEND, "send error"), \ + PARA_ERROR(RECV, "receive error"), \ + PARA_ERROR(SOCKET, "socket error"), \ + PARA_ERROR(CONNECT, "connect error"), \ + PARA_ERROR(ACCEPT, "accept error"), \ + PARA_ERROR(SETSOCKOPT, "failed to set socket options"), \ + PARA_ERROR(BIND, "bind error"), \ + PARA_ERROR(NAME_TOO_LONG, "name too long for struct sockaddr_un"), \ + PARA_ERROR(CHMOD, "failed to set socket mode"), \ + PARA_ERROR(SENDMSG, "sendmsg() failed"), \ + PARA_ERROR(RECVMSG, "recvmsg() failed"), \ + PARA_ERROR(SCM_CREDENTIALS, "did not receive SCM credentials"), \ + PARA_ERROR(LISTEN, "listen error"), \ + PARA_ERROR(RECV_PATTERN, "did not receive expected pattern"), \ + PARA_ERROR(HOST_INFO, "gethostbyname() failed"), \ + + +#define ORTP_RECV_ERRORS \ + PARA_ERROR(MSG_TO_BUF, "failed to extract rtp packet"), \ + PARA_ERROR(ORTP_SYNTAX, "ottp syntax error"), \ + PARA_ERROR(TOO_MANY_BAD_CHUNKS, "too many consecutive bad chunks"), \ + PARA_ERROR(INVALID_HEADER, "invalid header packet"), \ + PARA_ERROR(OVERRUN, "outout buffer overrun"), \ + + +#define HTTP_RECV_ERRORS \ + PARA_ERROR(SEND_HTTP_REQUEST, "failed to send http request"), \ + PARA_ERROR(MISSING_OK, "did not receive OK message from peer"), \ + PARA_ERROR(HTTP_RECV_BUF, "did not receive buffer") + + +#define RECV_ERRORS \ + PARA_ERROR(RECV_SELECT, "recv select error"), \ + PARA_ERROR(WRITE_STDOUT, "stdout write error"), \ + + +#define RECV_COMMON_ERRORS \ + PARA_ERROR(RECV_SYNTAX, "recv syntax error"), \ + + +#define AUDIOD_ERRORS \ + PARA_ERROR(WRITE_AUDIO_DATA, "failed to write audio data"), \ + PARA_ERROR(NO_MORE_SLOTS, "no more empty slots"), \ + PARA_ERROR(MISSING_COLON, "syntax error: missing colon"), \ + PARA_ERROR(UNSUPPORTED_AUDIO_FORMAT, "given audio format not supported"), \ + PARA_ERROR(CLIENT_WRITE, "client write error"), \ + PARA_ERROR(UCRED_PERM, "permission denied"), \ + PARA_ERROR(INVALID_AUDIOD_CMD, "invalid command"), \ + + +#define FILTER_CHAIN_ERRORS \ + PARA_ERROR(UNSUPPORTED_FILTER, "given filter not supported"), \ + PARA_ERROR(BAD_FILTER_OPTIONS, "invalid filter option given"), \ + + +#define STAT_ERRORS \ + PARA_ERROR(TOO_MANY_CLIENTS, "maximal number of stat clients exceeded"), \ + PARA_ERROR(UNKNOWN_STAT_ITEM, "status item not recognized"), \ + + +#define OGGDEC_ERRORS \ + PARA_ERROR(OGGDEC_READ, "read from media returned an error"), \ + PARA_ERROR(OGGDEC_NOTVORBIS, "bitstream is not vorbis data"), \ + PARA_ERROR(OGGDEC_VERSION, "vorbis version mismatch"), \ + PARA_ERROR(OGGDEC_BADHEADER, "invalid vorbis bitstream header"), \ + PARA_ERROR(OGGDEC_FAULT, "bug or heap/stack corruption"), \ + PARA_ERROR(OGGDEC_BADLINK, "invalid stream section or requested link corrupt"), \ + + +#define GRAB_CLIENT_ERRORS \ + PARA_ERROR(PEDANTIC_GRAB, "fd not ready and pedantic grab requested"), \ + PARA_ERROR(GC_WRITE, "grab client write error"), \ + PARA_ERROR(INVALID_GRAB_MODE, "invalid grab client mode"), \ + PARA_ERROR(BAD_GC_SLOT, "invalid slot requested by grab client"), \ + PARA_ERROR(BAD_GC_FILTER_NUM, "invalid filter number given"), \ + PARA_ERROR(GC_SYNTAX, "grab client syntax error"), \ + PARA_ERROR(GC_HELP_GIVEN, ""), /* not really an error */ \ + + +#define MP3DEC_ERRORS \ + PARA_ERROR(MAD_FRAME_DECODE, "mad frame decode error"), \ + PARA_ERROR(MP3DEC_OVERRUN, "mp3 output buffer overrun"), \ + + +#define FILTER_ERRORS \ + PARA_ERROR(NO_FILTERS, "at least one filter must be given"), \ + PARA_ERROR(FILTER_SYNTAX, "syntax error"), \ + + +#define SIGNAL_ERRORS \ + PARA_ERROR(SIGNAL_SIG_ERR, "signal() retured SIG_ERR"), \ + PARA_ERROR(SIGNAL_READ, "read error from signal pipe"), \ + PARA_ERROR(WAITPID, "waitpid error"), \ + + +#define STRING_ERRORS \ + PARA_ERROR(MKSTEMP, "mkstemp error: unable to create tmp file"), \ + PARA_ERROR(FCHMOD, "fchmod error: can not set mode"), \ + + +#define EXEC_ERRORS \ + PARA_ERROR(DUP_PIPE, "exec error: can not create pipe"), \ + PARA_ERROR(NULL_OPEN, "can not open /dev/null"), \ + + +#define MP3_ERRORS \ + PARA_ERROR(FREAD, "fread error"), \ + PARA_ERROR(FSEEK, "fseek error"), \ + PARA_ERROR(FRAME, "invalid mp3 frame"), \ + PARA_ERROR(FRAME_LENGTH, "invalid frame length"), \ + PARA_ERROR(MP3_NO_FILE, "invalid mp3 file pointer"), \ + PARA_ERROR(MP3_INFO, "could not read mp3 info"), \ + PARA_ERROR(MP3_REPOS, "mp3 repositioning error"), \ + PARA_ERROR(HEADER_FREQ, "invalid header frequency"), \ + PARA_ERROR(HEADER_BITRATE, "invalid header bitrate"), \ + + +#define OGG_ERRORS \ + PARA_ERROR(OGG_READ, "ogg read error"), \ + PARA_ERROR(SYNC_PAGEOUT, "ogg sync page-out error (no ogg file?)"), \ + PARA_ERROR(STREAM_PAGEIN, "ogg stream page-in error (first page)"), \ + PARA_ERROR(STREAM_PACKETOUT, "ogg stream packet-out error (first packet)"), \ + PARA_ERROR(VORBIS, "vorbis synthesis header-in error (not vorbis?)"), \ + PARA_ERROR(OGG_NO_FILE, "invalid ogg file pointer"), \ + PARA_ERROR(OGG_OPEN, "ov_open error"), \ + PARA_ERROR(OGG_INFO, "ov_info error"), \ + PARA_ERROR(OGG_REPOS, "ogg repositioning error"), \ + + +#define AFS_ERRORS \ + PARA_ERROR(AUDIO_FORMAT, "audio format not recognized"), \ + PARA_ERROR(FSTAT, "failed to fstat() audio file"), \ + + +#define DB_ERRORS \ + PARA_ERROR(GETCWD, "can not get current working directory"), \ + PARA_ERROR(CHDIR, "can not change directory"), \ + PARA_ERROR(OPENDIR, "can not open directory"), \ + PARA_ERROR(LSTAT, "lstat error"), \ + + +#define CRYPT_ERRORS \ + PARA_ERROR(PRIVATE_KEY, "can not read private key"), \ + PARA_ERROR(PUBLIC_KEY, "can not read public key"), \ + PARA_ERROR(RSA, "RSA error"), \ + PARA_ERROR(ENCRYPT, "encrypt error"), \ + PARA_ERROR(DECRYPT, "decrypt error"), \ + PARA_ERROR(CHALLENGE, "failed to read challenge"), \ + + +#define HTTP_SEND_ERRORS \ + PARA_ERROR(QUEUE, "packet queue overrun"), \ + PARA_ERROR(WRITE_OK, "can not check whether fd is writable"), \ + + +#define DOPEY_ERRORS \ + PARA_ERROR(FILE_COUNT, "audio file count exceeded"), \ + PARA_ERROR(NOTHING_FOUND, "no audio files found"), \ + + +#define MYSQL_ERRORS \ + PARA_ERROR(MYSQL_SYNTAX, "mysql syntax error"), \ + PARA_ERROR(NOTCONN, "not connected to mysql server"), \ + PARA_ERROR(TOOBIG, "mysql: file too large"), \ + PARA_ERROR(NAMETOOLONG, "mysql: name too long"), \ + PARA_ERROR(QFAILED, "mysql query failed"), \ + PARA_ERROR(NOROW, "row is NULL"), \ + PARA_ERROR(NOATTS, "can not get attributes from mysql table"), \ + PARA_ERROR(NORESULT, "error while fetching mysql result"), \ + PARA_ERROR(EMPTY_RESULT, "result is empty"), \ + PARA_ERROR(ESCAPE, "can not escape string"), \ + PARA_ERROR(GET_AUDIO_FILE, "can not get current audio file"), \ + PARA_ERROR(GET_STREAM, "can not get current stream"), \ + PARA_ERROR(NO_STREAM, "no such stream"), \ + PARA_ERROR(GET_QUERY, "can not get query for specified stream"), \ + PARA_ERROR(TMPFILE, "error while writing temporary file"), \ + PARA_ERROR(META, "can not get meta data"), \ + PARA_ERROR(MYSQL_INIT, "can not initialize mysql connection"), \ + PARA_ERROR(NO_MYSQL_PASSWD, "fatal: no mysql passord given"), \ + PARA_ERROR(NO_AF_DIR, "fatal: audio file directory not given"), \ + + +#define COMMAND_ERRORS \ + PARA_ERROR(COMMAND_SYNTAX, "syntax error in command"), \ + PARA_ERROR(AUTH, "did not receive auth request"), \ + PARA_ERROR(BAD_DBTOOL, "no such database tool"), \ + PARA_ERROR(NO_AUDIO_FILE, "no audio file"), \ + PARA_ERROR(BAD_CMD, "invalid command"), \ + PARA_ERROR(PERM, "permission denied"), \ + PARA_ERROR(USERLIST, "failed to open user list file"), \ + PARA_ERROR(BAD_USER, "you don't exist. Go away."), \ + PARA_ERROR(LOCK, "lock error"), \ + PARA_ERROR(SENDER_CMD, "command not supported by this sender"), \ + + +/* these do not need error handling (yet) */ +#define SERVER_ERRORS +#define WAV_ERRORS +#define COMPRESS_ERRORS +#define TIME_ERRORS +#define CLOSE_ON_FORK_ERRORS +#define DAEMON_ERRORS +#define ORTP_SEND_ERRORS +#define RINGBUFFER_ERRORS + + +/** + * the subsystem shift + * + * 255 error codes ought to be enough for every subsystem. Use the higher bits + * of the return value to encode the subsystem number + */ +#define SS_SHIFT 8 + +/** + * compute the subsystem offset + * + * It is given by x * 2**8 where \a x is the subsystem number + */ +#define SS_OFFSET(ss) (SS_ ## ss << SS_SHIFT) + +/** + * make the enum of all errors of one subsystem + * + * As zero should not be an error, we define a dummy enum entry with value + * 2**ss. That lets the real errors start at 2**ss + 1. + */ +#define SS_ENUM(ss) enum {\ + E_ ## ss ## _DUMMY = SS_OFFSET(ss), \ + ss ## _ERRORS} + +/** + * determine the subsystem number from the error number + * + * Easy, it's just \a num / 2**8. + */ +#define ERRNUM_TO_SS(num) ((num) >> SS_SHIFT) + +/** + * determine the index of an error number + * + * Also easy: It's the lower 8 bits of num - 1. + */ +#define ERRNUM_TO_INDEX(num) (((1 << SS_SHIFT) - 1) & ((num) - 1)) + +/** + * paraslash's version of strerror(3) + * + * expands to the error text of \a num (a string constant). + */ +#define PARA_STRERROR(num) para_errlist[ERRNUM_TO_SS(num)] [ERRNUM_TO_INDEX(num)] + +/** + * define the error list for one subsystem + * + * Used by macros in config.h (generated by configure) + */ +#define DEFINE_ERRLIST(ss) const char * ss ## _ERRLIST[] = {ss ## _ERRORS} + +/** + * activate errors for one subsystem. + * + * Each executable needs only the error lists of those subssystems it is actually + * linked with. We always reserve zeroed-out space for NUM_SS char ** pointers, but + * only init those of the needed subsystems. This macro is used by macros in config.h + * (generated by configure). + */ +#define PARA_ERRLIST(ss) [SS_ ## ss] = ss ## _ERRLIST + +/** + * This is temporarily defined to expand to its second argument (prefixed by + * 'E_') and gets later redefined to expand to the error text only + */ +#define PARA_ERROR(err, msg) E_ ## err + +/** \cond popcorn time */ +SS_ENUM(ORTP_RECV); +SS_ENUM(NET); +SS_ENUM(RECV); +SS_ENUM(AUDIOD); +SS_ENUM(EXEC); +SS_ENUM(CLOSE_ON_FORK); +SS_ENUM(SIGNAL); +SS_ENUM(STRING); +SS_ENUM(DAEMON); +SS_ENUM(STAT); +SS_ENUM(TIME); +SS_ENUM(GRAB_CLIENT); +SS_ENUM(HTTP_RECV); +SS_ENUM(RECV_COMMON); +SS_ENUM(FILTER_CHAIN); +SS_ENUM(WAV); +SS_ENUM(COMPRESS); +SS_ENUM(OGGDEC); +SS_ENUM(MP3DEC); +SS_ENUM(FILTER); +SS_ENUM(MP3); +SS_ENUM(OGG); +SS_ENUM(SERVER); +SS_ENUM(AFS); +SS_ENUM(COMMAND); +SS_ENUM(DOPEY); +SS_ENUM(CRYPT); +SS_ENUM(HTTP_SEND); +SS_ENUM(ORTP_SEND); +SS_ENUM(DB); +SS_ENUM(MYSQL); +SS_ENUM(RINGBUFFER); +/** \endcond */ +#undef PARA_ERROR +/* rest of the world only sees the error text */ +#define PARA_ERROR(err, msg) msg diff --git a/exec.c b/exec.c new file mode 100644 index 00000000..34643b8f --- /dev/null +++ b/exec.c @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2003-2006 Andre Noll + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ + +/** \file exec.c helper functions for spawning new processes */ +#include "para.h" +#include "close_on_fork.h" +#include "error.h" +#include "string.h" + +/** + * spawn a new process and redirect fd 0, 1, and 2 + * + * \param pid will hold the pid of the created process upon return + * \param file path of the executable to execute + * \param args the argument array for the command + * \param fds a pointer to a value-result array + * + * This function uses fork/exec to create a new process. \a fds must be a + * pointer to three integers, corresponding to stdin, stdout and stderr + * respectively. It specifies how to deal with fd 0, 1, 2 in the child. The + * contents of \a fds are interpreted as follows: + * + * - fd[i] < 0: leave fd \a i alone + * - fd[i] = 0: dup fd \a i to /dev/null + * - fd[i] > 0: create a pipe and dup i to one end of that pipe. + * Upon return, fd[i] contains the file descriptor of the pipe. + * + * In any case, all unneeded filedescriptors are closed. + * + * \return Negative on errors, positive on success. + * + * \sa null(4), pipe(2), dup2(2), fork(2), exec(3) + */ +int para_exec(pid_t *pid, const char *file, char *const args[], int *fds) +{ + int ret, in[2] = {-1, -1}, out[2] = {-1, -1}, err[2] = {-1, -1}, + null = -1; /* ;) */ + + ret = -E_DUP_PIPE; + if (fds[0] > 0 && pipe(in) < 0) + goto err_out; + if (fds[1] > 0 && pipe(out) < 0) + goto err_out; + if (fds[2] > 0 && pipe(err) < 0) + goto err_out; + if (!fds[0] || !fds[1] || !fds[2]) { + ret = -E_NULL_OPEN; + null = open("/dev/null", O_RDONLY); + if (null < 0) + goto err_out; + } + if ((*pid = fork()) < 0) + exit(EXIT_FAILURE); + if (!(*pid)) { /* child */ + close_listed_fds(); /* close unneeded fds */ + if (fds[0] >= 0) { + if (fds[0]) { + close(in[1]); + if (in[0] != STDIN_FILENO) + dup2(in[0], STDIN_FILENO); + } else + dup2(null, STDIN_FILENO); + } + if (fds[1] >= 0) { + if (fds[1]) { + close(out[0]); + if (out[1] != STDOUT_FILENO) + dup2(out[1], STDOUT_FILENO); + } else + dup2(null, STDOUT_FILENO); + } + if (fds[2] >= 0) { + if (fds[2]) { + close(err[0]); + if (err[1] != STDERR_FILENO) + dup2(err[1], STDERR_FILENO); + } else + dup2(null, STDERR_FILENO); + } + if (null >= 0) + close(null); + execvp(file, args); + _exit(EXIT_FAILURE); + } + if (fds[0] > 0) { + close(in[0]); + *fds = in[1]; + } + if (fds[1] > 0) { + close(out[1]); + *(fds + 1) = out[0]; + } + if (fds[2] > 0) { + close(err[1]); + *(fds + 2) = err[0]; + } + if (null >= 0) + close(null); + return 1; +err_out: + if (err[0] >= 0) + close(err[0]); + if (err[1] >= 0) + close(err[1]); + if (out[0] >= 0) + close(out[0]); + if (out[1] >= 0) + close(out[1]); + if (in[0] >= 0) + close(in[0]); + if (in[1] >= 0) + close(in[1]); + if (null >= 0) + close(null); + return ret; +} + + +/** + * exec the given command + * + * \param pid the same meaning as in para_exec() + * \param cmdline holds the command and its arguments, seperated by spaces + * \param fds the same meaning as in para_exec() + * + * A wrapper for para_exec() which calls split_args() to seperate + * the command line arguments. + * + * \return positive on success, negative on errors + */ +int para_exec_cmdline_pid(pid_t *pid, char *cmdline, int *fds) +{ + int argc, ret; + char **argv, *tmp = para_strdup(cmdline); + + if (!tmp) + exit(EXIT_FAILURE); + argc = split_args(tmp, &argv, ' '); + ret = para_exec(pid, argv[0], argv, fds); + free(argv); + free(tmp); + return ret; +} + +/** + * check whether a file exists + * + * \param fn the file name + * + * \return Non-zero iff file exists. + */ +int file_exists(const char *fn) +{ + struct stat statbuf; + + return !stat(fn, &statbuf); +} diff --git a/fade.c b/fade.c new file mode 100644 index 00000000..1f66b363 --- /dev/null +++ b/fade.c @@ -0,0 +1,314 @@ +/* + * Copyright (C) 1998-2005 Andre Noll + * + * 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 +#include +#include /* EXIT_SUCCESS */ +#include +#include +#include +#include +#include +#include "string.h" + + +struct gengetopt_args_info args_info; + +void para_log(__unused int ll, char *fmt,...) +{ + va_list argp; + time_t t1; + struct tm *tm; + + time(&t1); + tm = localtime(&t1); + printf("%d:%02d:%02d ", tm->tm_hour, tm->tm_min, tm->tm_sec); + va_start(argp, fmt); + vprintf(fmt, argp); + va_end(argp); +} + +/* + * open mixer device + */ +static int open_mixer(void) +{ + int mixer_fd; + + if ((mixer_fd = open(args_info.mixer_device_arg, O_RDWR, 0)) < 0) + return -1; + return mixer_fd; +} + +/* + * get volume via mixer_fd + */ +static int do_get_vol(int mixer_fd) +{ + int volume; + + if (ioctl(mixer_fd, MIXER_READ(SOUND_MIXER_VOLUME), &volume) < 0) + return -1; + /* take the mean value of left and right volume */ + return (volume % 256 + (volume >> 8)) / 2; +} + +/* + * open mixer, get volume and close mixer + */ +static int get_vol(void) +{ + int mixer_fd; + int volume; + + mixer_fd = open_mixer(); + if (mixer_fd < 0) + return -1; + volume = do_get_vol(mixer_fd); + close(mixer_fd); + return volume; +} + +/* + * set volume via mixer_fd + */ +static int do_set_vol(int mixer_fd, int volume) +{ + int tmp; + tmp = (volume << 8) + volume; + if (ioctl(mixer_fd, MIXER_WRITE(SOUND_MIXER_VOLUME), &tmp) < 0) + return 0; + return 1; +} + +/* + * open mixer, set volume and close mixer + */ +static int set_vol(int volume) +{ + int mixer_fd; + int ret; + + mixer_fd = open_mixer(); + ret = 0; + if (mixer_fd < 0) + goto out; + if (!do_set_vol(mixer_fd, volume)) + goto out; + ret = 1; + close(mixer_fd); +out: + return ret; +} + +/* + * Open mixer, get volume, fade to new_vol in secs seconds and + * close mixer + */ +static int fade(int new_vol, unsigned int secs) +{ + int vol, mixer_fd = -1, diff, incr, ret; + struct timespec ts; + unsigned long long tmp, tmp2; /* Careful with that axe, Eugene! */ + + PARA_NOTICE_LOG("fading to %d in %d seconds\n", new_vol, secs); + ret = 0; + if (!secs) + goto out; + mixer_fd = open_mixer(); + if (mixer_fd < 0) + goto out; + vol = do_get_vol(mixer_fd); + if (vol < 0) + goto out; + diff = new_vol - vol; + if (!diff) { + sleep(secs); + ret = 1; + goto out; + } + incr = diff > 0? 1: -1; + diff = diff > 0? diff: -diff; + tmp = secs * 1000 / diff; + tmp2 = tmp % 1000; + while ((new_vol - vol) * incr > 0) { + ts.tv_nsec = tmp2 * 1000000; /* really nec ?*/ + ts.tv_sec = tmp / 1000; /* really nec ?*/ + //printf("ts.tv_sec: %i\n", ts.tv_nsec); + vol += incr; + if (!do_set_vol(mixer_fd, vol)) + goto out; + //printf("vol = %i\n", vol); + nanosleep(&ts, NULL); + } +out: + if (mixer_fd >= 0) + close(mixer_fd); + return 1; +} + +static int client_cmd(char *cmd,...) +{ + int ret, fds[3] = {0, 0, 0}; + pid_t pid; + char *cmdline = make_message(BINDIR "/para_client %s", cmd); + PARA_INFO_LOG("%s", cmdline); + ret = para_exec_cmdline_pid(&pid, cmdline, fds); + free(cmdline); + return ret; +} + +/* + * sleep + */ +static void sweet_dreams(void) +{ + time_t t1, wake_time_epoch; + unsigned int delay; + struct tm *tm; + int hour = args_info.wake_hour_arg; + int min = args_info.wake_min_arg; + char *fa_stream = args_info.fa_stream_arg; + char *wake_stream = args_info.wake_stream_arg; + //char *current_stream = stat_items[STREAM].content; + int wf = args_info.wake_fade_arg; + int sf = args_info.fa_fade_arg; + int wv = args_info.wake_vol_arg; + int sv = args_info.fa_vol_arg; + int iv = args_info.sleep_ivol_arg; + char *cmd, *sleep_stream = args_info.sleep_stream_given? + args_info.sleep_stream_arg : NULL; + + if (sf) { + PARA_INFO_LOG("initial volume: %d\n", iv); + set_vol(iv); + cmd = make_message("csp %s\n", fa_stream); + client_cmd(cmd); + free(cmd); + fade(sv, sf); + } + if (sleep_stream) { + cmd = make_message("csp %s\n", sleep_stream); + client_cmd(cmd); + free(cmd); + } else + client_cmd("stop"); + if (!wf) + return; + /* calculate wake time */ + time(&t1); + tm = localtime(&t1); + if (tm->tm_hour > hour || (tm->tm_hour == hour && tm->tm_min> min)) { + /* wake time is tomorrow */ + t1 += 86400; + tm = localtime(&t1); + t1 -= 86400; + } + tm->tm_hour = hour; + tm->tm_min = min; + tm->tm_sec = 0; + wake_time_epoch = mktime(tm); + PARA_INFO_LOG("waketime: %s", asctime(tm)); + while (wake_time_epoch > t1 + wf) { + delay = wake_time_epoch - t1 - wf; + PARA_INFO_LOG("sleeping %u seconds (%u:%02u)\n", + delay, delay / 3600, + (delay % 3600) / 60); + sleep(delay); + time(&t1); + } + cmd = make_message("csp %s\n", wake_stream); + client_cmd(cmd); + free(cmd); + fade(wv, wf); + PARA_INFO_LOG("%s", "fade complete, returning\n"); +} + +static void snooze(void) +{ + if (get_vol() < args_info.snooze_out_vol_arg) + set_vol(args_info.snooze_out_vol_arg); + else + fade(args_info.snooze_out_vol_arg, args_info.snooze_out_fade_arg); + client_cmd("pause"); + PARA_NOTICE_LOG("%d seconds snooze time...\n", args_info.snooze_time_arg); + sleep(args_info.snooze_time_arg); + client_cmd("play"); + fade(args_info.snooze_in_vol_arg, args_info.snooze_in_fade_arg); +} + +static int configfile_exists(void) +{ + static char *config_file; + + if (!args_info.config_file_given) { + char *home = para_homedir(); + const char *conf = ".paraslash/fade.conf"; + free(config_file); + config_file = make_message("%s/%s", home, conf); + free(home); + args_info.config_file_arg = config_file; + } + return file_exists(args_info.config_file_arg); +} + + +int main(int argc, char *argv[]) +{ + int ret; + + if (cmdline_parser(argc, argv, &args_info)) + exit(EXIT_FAILURE); + ret = configfile_exists(); + if (!ret && args_info.config_file_given) { + PARA_EMERG_LOG("can not read config file %s\n", + args_info.config_file_arg); + exit(EXIT_FAILURE); + } + if (ret) + cmdline_parser_configfile(args_info.config_file_arg, + &args_info, 0, 0, 0); + if ((ret = open_mixer()) < 0) { + PARA_EMERG_LOG("can not open mixer device %s.", + args_info.mixer_device_arg); + exit(EXIT_FAILURE); + } else + close(ret); + ret = 0; + setlinebuf(stdout); + if (!strcmp(args_info.mode_arg, "sleep")) { + sweet_dreams(); + goto out; + } + if (!strcmp(args_info.mode_arg, "fade")) { + fade(args_info.fade_vol_arg, args_info.fade_time_arg); + goto out; + } + if (!strcmp(args_info.mode_arg, "snooze")) { + snooze(); + goto out; + } + ret = -1; +out: + return ret; +} diff --git a/fade.ggo b/fade.ggo new file mode 100644 index 00000000..c57267da --- /dev/null +++ b/fade.ggo @@ -0,0 +1,27 @@ +section "general options" +option "mode" o "{sleep|fade|snooze}" string default="sleep" no +option "config_file" c "(default='~/.paraslash/fade.conf')" string typestr="filename" no +option "mixer_device" m "mixer device file" string typestr="device" default="/dev/mixer" no + +section "sleep options (only relevant in sleep mode)" +option "sleep_ivol" - "set initial volume before doing anything else" int typestr="volume" default="60" no +option "fa_stream" - "fall asleep stream. Change to this stream right after setting the volume" string typestr="streamname" default="fa" no +option "fa_fade" - "fall asleep fading time, no fading if set to 0" int typestr="seconds" default="1800" no +option "fa_vol" - "volume to fade to" int typestr="volume" default="20" no +option "sleep_stream" - "change to this stream after fading, stop playing if unset" string typestr="streamname" default="sleep" no +option "wake_hour" H "(0-23)" int default="8" no +option "wake_min" M "(0-59)" int default="0" no +option "wake_stream" - "changed to on waketime" string typestr="streamname" default="wake" no +option "wake_fade" - "no fading in if set to 0" int typestr="seconds" default="1200" no +option "wake_vol" - "vol to fade to at waketime" int typestr="volume" default="80" no + +section "snooze options" +option "snooze_out_fade" - "fade out time" int typestr="seconds" default="30" no +option "snooze_out_vol" - "vol to fade to before snooze" int typestr="volume" default="20" no +option "snooze_time" - "delay" int typestr="seconds" default="600" no +option "snooze_in_fade" - "fade in time" int typestr="seconds" default="180" no +option "snooze_in_vol" - "vol to fade to after snooze" int typestr="volume" default="80" no + +section "fade options" +option "fade_vol" f "volume to fade to" int typestr="volume" default="50" no +option "fade_time" t "time to fade in" int typestr="seconds" default="5" no diff --git a/filter.c b/filter.c new file mode 100644 index 00000000..2e5e205e --- /dev/null +++ b/filter.c @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2005-2006 Andre Noll + * + * 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:-h for help on \n"); + exit(EXIT_SUCCESS); +} + +int main(int argc, char *argv[]) +{ + int converted, ret; + char *ib, *ob; /* input/output buffer */ + size_t *il, *ol; /* number of loaded bytes in input/output buffer */ + + filter_init(filters); + ret = parse_config(argc, argv); + if (ret < 0) + goto out; + inbuf = para_malloc(INBUF_SIZE); + ret = init_active_filter_list(); + if (ret < 0) + goto out; + open_filters(); + ib = fci->inbuf; + ob = fci->outbuf; + il = fci->in_loaded; + ol = fci->out_loaded; + PARA_DEBUG_LOG("ib %p in, ob: %p\n", ib, ob); +again: + if (*il < INBUF_SIZE && !eof) { + ret = read(STDIN_FILENO, ib + *il, INBUF_SIZE - *il); + PARA_DEBUG_LOG("read %d/%d\n", ret, INBUF_SIZE - *il); + if (ret < 0) + goto out; + if (!ret) + eof = 1; + *il += ret; + } + ret = filter_io(fci); + if (ret < 0) + goto out; + converted = ret; + if (*ol) { + ret = write(STDOUT_FILENO, ob, *ol); + PARA_DEBUG_LOG("wrote %d/%d\n", ret, *ol); + if (ret <= 0) + goto out; + *ol -= ret; + if (*ol) { + PARA_NOTICE_LOG("short write: %d bytes left\n", *ol); + memmove(ob, ob + ret, *ol); + } + } + if (!eof || converted) + goto again; + ret = 0; +out: + if (ret < 0) + PARA_EMERG_LOG("%s\n", PARA_STRERROR(-ret)); + close_filters(fci); + return ret; +} diff --git a/filter.ggo b/filter.ggo new file mode 100644 index 00000000..4157835c --- /dev/null +++ b/filter.ggo @@ -0,0 +1,18 @@ +option "loglevel" l "set loglevel (0-6)" int typestr="level" default="4" no +option "filter" f "Specify filter. + +May be given multiple times to 'pipe' the stream +through arbitrary many filters in an efficient +way. The same filter may appear more than once, +order matters. + +Filter options may be specified for each '-f' +option separately. Insinde these options ':' +must be used as the separator instead of white +space. Example: + + -f compress:--anticlip:--volume:2 +" +string typestr="filter_spec" no multiple + +option "list_filters" L "print list of available filters and exit" flag off diff --git a/filter.h b/filter.h new file mode 100644 index 00000000..70f4f4fe --- /dev/null +++ b/filter.h @@ -0,0 +1,349 @@ +/* + * Copyright (C) 2005-2006 Andre Noll + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ + +/** \file filter.h filter-related structures and exported symbols from filter_chain.c */ + +/** + * describes one running instance of a chain of filters + * + */ +struct filter_chain_info { +/** + * + * + * the number of channels of the current stream + * + * Set by the decoding filter + */ + unsigned int channels; +/** + * + * + * current samplerate in Hz + * + * Set by the decoding filter + */ + unsigned int samplerate; +/** + * + * + * the list containing all filter nodes in this filter chain + */ + struct list_head filters; +/** + * + * + * the input buffer of the filter chain + * + * This is set to point to the output buffer of the receiving application (the + * buffer used to read from stdin for para_filter; the output buffer of the + * current receiver for para_audiod) + */ + char *inbuf; +/** + * + * + * the output buffer of the filter chain + * + * Points to the output buffer of the last filter in the filter chain +**/ + char *outbuf; +/** + * + * + * pointer to variable containing the number of bytes loaded in the input buffer + */ + size_t *in_loaded; +/** + * + * + * pointer to variable containing the number of bytes loaded in the output buffer + */ + size_t *out_loaded; +/** + * + * + * non-zero if end of file was encountered + */ + int *eof; +/** + * + * + * non-zero if an error occured + */ + int error; +}; + +/** + * describes one running instance of a filter +*/ +struct filter_node { +/** + * + * + * a pointer to the corresponding filter struct + */ + struct filter *filter; +/** + * + * + * the filter chain this filter node belongs to + */ + struct filter_chain_info *fci; +/** + * + * + * the position of the filter in the corresponding filter chain + * + * all filters that make up the filter chains are organized in a doubly + * linked list. + */ + struct list_head node; +/** + * + * + * each filter may store any filter-specific information about the particular + * instance of the filter here. + */ + void *private_data; +/** + * + * + * the output buffer + */ + char *buf; +/** + * the size of the output buffer + */ + size_t bufsize; +/** + * + * + * the number of bytes currently loaded in \a buf + */ + size_t loaded; +/** + * + * + * the list of registered callbacks + */ + struct list_head callbacks; +/** + * + * a pointer to the configuration of this instance + */ + void *conf; +}; + +/** + * used to manage grab clients + * + * An application using paraslash's filter subsystem may register any number of + * callbacks for each filter_node. It is possible to attach a filter callback + * while the filter is running. This is used for stream grabbing in + * para_audiod: Whenever a client sends the 'grab' command, para_audiod adds a + * filter callback to the list of callbacks for the filter node specified in + * the grab command. + */ +struct filter_callback { +/** + * + * + * all callbacks are organized in a doubly linked list + */ + struct list_head node; +/** + * + * + * private data + * + * May be initialized by the application before registering the callback. This + * pointer is not used by the filter subsystem. It is provided for use within + * the input/ouput/close callback functions. + */ + void *data; +/** + * + * + * the input callback + * + * In not \p NULL, the filter subsystem calls this function whenever the filter + * consumed some or all of its input buffer. A pointer to the buffer of consumed + * data, its length and a pointer to the own \a filter_callback structure are passed + * to \a input_cb. The input callback is expected to return a negative value on errors. + */ + int (*input_cb)(char *buf, size_t len, struct filter_callback *fc); +/** + * + * + * the output callback + * + * If not NULL, this is called whenever the filter produces output. A pointer + * to the output data, its length and a pointer to the own \a filter_callback + * structure are passed to \a output_cb. Like the input callback, the output + * callback is expected to return a negative value on errors. + */ + int (*output_cb)(char *buf, size_t len, struct filter_callback *fc); +/** + * + * + * the callback close function + * + * This gets called whenever the input/ouput callback returned an error, or if + * the filter chain is going to be destroyed, e.g. because the end of the + * stream was encounterd. It is assumed to succeed. + */ + void (*close)(struct filter_callback *fc); +}; + + +void close_filters(struct filter_chain_info *fci); +int filter_io(struct filter_chain_info *fci); +void filter_init(struct filter *all_filters); +int check_filter_arg(char *filter_arg, void **conf); +int del_filter_callback(struct filter_callback *fcb); + +/** + * the structure associated with a paraslash filter + * + * Paraslash filters are "modules" which are used to transform an audio stream. + * struct filter contains pointers to functions that must be supplied by the + * filter code in order to be used by the driving application (currently + * para_audiod and para_filter). + * + * Note: As several instances of the same filter may be running at the same + * time, all these filter functions must be reentrant; no static non-constant + * variables may be used. + * \sa mp3dec.c, oggdec.c, wav.c, compress.c, filter_node + */ +struct filter { +/** + * + * + * the name of the filter + */ +const char *name; +/** + * + * + * pointer to the filter init routine + * + * This function is only called once at startup. It must initialize the + * other non-optional function pointers of \a f. + */ +void (*init)(struct filter *f); +/** + * + * + * open one instance of this filter + * + * This should allocate the output buffer of the given filter node and do any + * other filter-specific preparations like initializing the private_data member + * of \a fn suitably. The open function is assumed to succeed. + */ +void (*open)(struct filter_node *fn); +/** + * + * + * convert (filter) the given data + * + * Pointer to the converting function of the filter. It should convert the + * given input buffer \a inbuf which is of length \a len to the previoulsy + * reserved output buffer of \a fn. On success, it must return the number of + * bytes it consumed from \a inbuf. On errors, a negative number indicating the + * kind of the error must be returned. + * + * A zero return value just means that nothing was converted (probably because + * the input buffer was too small). This is not interpreted as an error. + */ +ssize_t (*convert)(char *inbuf, size_t len, struct filter_node *fn); +/** + * + * + * close one instance of this filter + * + * Free all resources of associated with \a fn that were previously allocated + * by the open() function. + */ +void (*close)(struct filter_node *fn); +/** + * + * + * print the help text for this filter and exit + * + * This is optional and it is not necessary to initialize this pointer if + * the filter does not have a help text. + */ +void (*print_help)(void); +/** + * + * + * a pointer to the filter's command line parser + * + * If this optional function pointer is not NULL, any filter options are passed + * from the main propgram to this command line parser once at application + * startup. The command line parser should check its command line options given + * by \a argc and \a argv and abort on errors. On success, it should return a + * pointer to the filter-specific configuration data determined by \a argc and + * \a argv. + */ +void *(*parse_config)(int argc, char **argv); +}; +/** \cond */ +extern struct filter filters[]; +#define DECLARE_EXTERN_FILTER_INIT(name) \ + extern void name ## _init(struct filter *f) + +#define FILTER_INIT(filter) { \ + .name = #filter, \ + .init = filter ## _init, \ + .parse_config = NULL, \ + .print_help = NULL \ +}, + +/* filters that are always present */ +DECLARE_EXTERN_FILTER_INIT(wav); +/* wav is always the first filter */ +#define WAV_FILTER_NUM 0 +DECLARE_EXTERN_FILTER_INIT(compress); + +/* next the optional filters */ +#ifdef HAVE_MAD +DECLARE_EXTERN_FILTER_INIT(mp3dec); +#define MP3DEC_FILTER FILTER_INIT(mp3dec) +#else +#define MP3DEC_FILTER +#endif + +#ifdef HAVE_OGGVORBIS +DECLARE_EXTERN_FILTER_INIT(oggdec); +#define OGGDEC_FILTER FILTER_INIT(oggdec) +#else +#define OGGDEC_FILTER +#endif + +/* + * a macro that defines an array of all available filters + */ +#define DEFINE_FILTER_ARRAY(fa) struct filter fa[] = { \ + FILTER_INIT(wav) \ + FILTER_INIT(compress) \ + MP3DEC_FILTER \ + OGGDEC_FILTER \ + { .name = NULL } }; +/** \endcond */ diff --git a/filter_chain.c b/filter_chain.c new file mode 100644 index 00000000..d22ef28e --- /dev/null +++ b/filter_chain.c @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2005-2006 Andre Noll + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ + +/** \file filter_chain.c common helper functions for filter input/output */ + +#include "gcc-compat.h" +#include "para.h" +#include "list.h" +#include "filter.h" +#include "error.h" +#include "string.h" + +DEFINE_FILTER_ARRAY(filters); + +/** + * call the init function of each supported filter + * + * \param all_filters the array of all supported filters + * \sa filter::init + */ +void filter_init(struct filter *all_filters) +{ + struct filter *f; + + for (f = all_filters; f->name; f++) + f->init(f); +} + +/** + * close and destroy a filter callback + * + * \param fcb the filter callback to close + * + * This removes \a fcb from the list of filter callbacks and calls + * the close callback associated with \a fcb. + */ +static void close_filter_callback(struct filter_callback *fcb) +{ + PARA_NOTICE_LOG("closing filter_callback %p, data: %p\n", fcb, fcb->data); + list_del(&fcb->node); + fcb->close(fcb); +} + +/** + * close all callbacks of a filter node + * + * \param fn the filter node which contains the filter callbacks to be closed + * + * Call close_filter_callback() for each entry in the filter callback list + * of \a fn. + */ +static void close_callbacks(struct filter_node *fn) +{ + struct filter_callback *fcb, *tmp; + + list_for_each_entry_safe(fcb, tmp, &fn->callbacks, node) + close_filter_callback(fcb); +} + +static void call_callbacks(struct filter_node *fn, char *inbuf, size_t inlen, + char *outbuf, size_t outlen) +{ + struct filter_callback *fcb, *tmp; + list_for_each_entry_safe(fcb, tmp, &fn->callbacks, node) { + int ret; + if (inlen && fcb->input_cb) { + ret = fcb->input_cb(inbuf, inlen, fcb); + if (ret < 0) { + close_filter_callback(fcb); + continue; + } + } + if (!outlen || !fcb->output_cb) + continue; + ret = fcb->output_cb(outbuf, outlen, fcb); + if (ret < 0) + close_filter_callback(fcb); + } +} + +/** + * call the convert function of each filter + * + * \param fci the filter chain containing the list of filter nodes. + * + * This is the core function of the filter subsystem. It loops over the list of + * filter nodes determined by \a fci and calls the filter's convert function if + * there is input available for the filter node in question. If the convert + * function consumed some or all of its input data, all registered input + * callbacks are called. Similarly, if a convert function produced output, all + * registerd output callbacks get called. + * + * \return The sum of output bytes produced by the convert functions on success, + * negative return value on errors. + * + * \sa filter_node, filter#convert, filter_callback + */ +int filter_io(struct filter_chain_info *fci) +{ + struct filter_node *fn; + char *ib; + size_t *loaded; + int conv, conv_total = 0; +again: + ib = fci->inbuf; + loaded = fci->in_loaded; + conv = 0; + list_for_each_entry(fn, &fci->filters, node) { + int ret; + if (*loaded && fn->loaded < fn->bufsize) { + size_t old_fn_loaded = fn->loaded; + PARA_DEBUG_LOG("fc %p loaded: %d, calling %s convert\n", fci, *loaded, fn->filter->name); + ret = fn->filter->convert(ib, *loaded, fn); + if (ret < 0) { + if (!fci->error) + fci->error = -ret; + return ret; + } + call_callbacks(fn, ib, ret, fn->buf + old_fn_loaded, fn->loaded - old_fn_loaded); + *loaded -= ret; + conv += ret; + if (*loaded && ret) { + PARA_DEBUG_LOG("moving %d bytes in input buffer for %s filter\n", + *loaded, fn->filter->name); + memmove(ib, ib + ret, *loaded); + } + } + ib = fn->buf; + loaded = &fn->loaded; + } +// PARA_DEBUG_LOG("loaded: %d\n", *loaded); + conv_total += conv; + if (conv) + goto again; + return conv_total; +} + +/** + * close all filter nodes and its callbacks + * + * \param fci the filter chain to close + * + * For each filter node determined by \a fci, call the close function of each + * registered filter callback as well as the close function of the + * corresponding filter. Free all resources and destroy all callback lists and + * the filter node list. + * + * \sa filter::close, filter_callback::close + */ +void close_filters(struct filter_chain_info *fci) +{ + struct filter_node *fn, *tmp; + + if (!fci) + return; + PARA_DEBUG_LOG("closing filter chain %p\n", fci); + list_for_each_entry_safe(fn, tmp, &fci->filters, node) { + PARA_NOTICE_LOG("closing %s filter callbacks (fci %p, fn %p)\n", fn->filter->name, fci, fn); + close_callbacks(fn); + PARA_NOTICE_LOG("closing %s filter (fci %p, fn %p)\n", fn->filter->name, fci, fn); + fn->filter->close(fn); + list_del(&fn->node); + free(fn); + } +} + +/* + * If the filter has a command line parser and options is not NULL, run it. + * Returns filter_num on success, negative on errors + */ +static int parse_filter_args(int filter_num, char *options, void **conf) +{ + struct filter *f = &filters[filter_num]; + int i, argc = 2; + char *dummy_args[] = {"", "", NULL}; + char **argv; + +// PARA_DEBUG_LOG("%s, options: %s, parser: %p\n", f->name, +// options? options : "(none)", f->parse_config); + if (!f->parse_config) + return options? -E_BAD_FILTER_OPTIONS : filter_num; + if (options) { +// PARA_DEBUG_LOG("options: %s\n", options); + argc = split_args(options, &argv, ':'); +// PARA_DEBUG_LOG("argc = %d, argv[0]: %s\n", argc, argv[0]); + for (i = argc; i >= 0; i--) + argv[i + 1] = argv[i]; + argc += 2; + *conf = f->parse_config(argc, argv); + } else { + /* is it OK to have no options? */ + *conf = f->parse_config(2, dummy_args); + } + return *conf? filter_num : -E_BAD_FILTER_OPTIONS; +} + +/** + * check the filter command line options + * + * \param fa the command line options (values separated by colons) + * \param conf points to the filter configuration upon successful return + * + * Check if \a fa starts with a the name of a supported filter, followed by + * a colon. If yes, call the command line parser of that filter. + * + * \return On success, the number of the filter is returned and \a conf + * is initialized to point to the filter configuration determined by \a fa. + * On errors, a negative value is returned. + * + * Note: If \a fa specifies a filter that has no command line parser success is + * returned, and \a conf is initialized to \p NULL. + * + * \sa filter::parse_config + */ +int check_filter_arg(char *fa, void **conf) +{ + int j; + + *conf = NULL; +// PARA_DEBUG_LOG("arg: %s\n", fa); + for (j = 0; filters[j].name; j++) { + const char *name = filters[j].name; + size_t len = strlen(name); + char c; + if (strlen(fa) < len) + continue; + if (strncmp(name, fa, len)) + continue; + c = fa[len]; + if (c && c != ':') + continue; + if (c && !filters[j].parse_config) + return -E_BAD_FILTER_OPTIONS; + return parse_filter_args(j, c? fa + len + 1 : NULL, conf); + } + return -E_UNSUPPORTED_FILTER; +} + diff --git a/fonts/24P_Arial_Blue.png b/fonts/24P_Arial_Blue.png new file mode 100644 index 00000000..bc71187f Binary files /dev/null and b/fonts/24P_Arial_Blue.png differ diff --git a/fonts/24P_Arial_Metallic_Yellow.png b/fonts/24P_Arial_Metallic_Yellow.png new file mode 100644 index 00000000..2eec3992 Binary files /dev/null and b/fonts/24P_Arial_Metallic_Yellow.png differ diff --git a/fonts/24P_Arial_NeonBlue.png b/fonts/24P_Arial_NeonBlue.png new file mode 100644 index 00000000..184218f6 Binary files /dev/null and b/fonts/24P_Arial_NeonBlue.png differ diff --git a/fonts/24P_Arial_NeonYellow.png b/fonts/24P_Arial_NeonYellow.png new file mode 100644 index 00000000..eecd4e52 Binary files /dev/null and b/fonts/24P_Arial_NeonYellow.png differ diff --git a/fonts/24P_Copperplate_Blue.png b/fonts/24P_Copperplate_Blue.png new file mode 100644 index 00000000..7d481956 Binary files /dev/null and b/fonts/24P_Copperplate_Blue.png differ diff --git a/gcc-compat.h b/gcc-compat.h new file mode 100644 index 00000000..a3b20d39 --- /dev/null +++ b/gcc-compat.h @@ -0,0 +1,43 @@ +#if __GNUC__ >= 3 +# define inline inline __attribute__ ((always_inline)) +# define __pure __attribute__ ((pure)) +# define __noreturn __attribute__ ((noreturn)) +# define __malloc __attribute__ ((malloc)) +# define __used __attribute__ ((used)) +# define __unused __attribute__ ((unused)) +# define __packed __attribute__ ((packed)) +# define likely(x) __builtin_expect (!!(x), 1) +# define unlikely(x) __builtin_expect (!!(x), 0) +/* + * p is the number of the "format string" parameter, and q is + * the number of the first variadic parameter + */ +# define __printf(p,q) __attribute__ ((format (printf, p, q))) +/* + * as direct use of __printf(p,q) confuses doxygen, here are two extra macros + * for those values p,q that are actually used by paraslash. + */ +#define __printf_1_2 __printf(1,2) +#define __printf_2_3 __printf(2,3) + +#else + +# define inline /* no inline */ +# define __pure /* no pure */ +# define __noreturn /* no noreturn */ +# define __malloc /* no malloc */ +# define __used /* no used */ +# define __unused /* no unused */ +# define __packed /* no packed */ +# define likely(x) (x) +# define unlikely(x) (x) +# define __printf(p,q) /* no format */ +#define __printf_1_2 +#define __printf_2_3 +#endif + +# if __GNUC__ >=3 && __GNUC_MINOR__ > 3 +# define __must_check __attribute__ ((warn_unused_result)) +# else +# define __must_check /* no warn_unused_result */ +# endif diff --git a/grab_client.c b/grab_client.c new file mode 100644 index 00000000..cd6cffb8 --- /dev/null +++ b/grab_client.c @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2006 Andre Noll + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ + +/** + * \file grab_client.c functions for grabbing the stream at any position + * in a filter chain + * + * \sa filter_chain filter_chain_info filter + */ + +#include "gcc-compat.h" +#include "para.h" +#include "close_on_fork.h" +#include "grab_client.cmdline.h" +#include "list.h" +#include "filter.h" +#include "grab_client.h" +#include "audiod.h" +#include "error.h" +#include "string.h" + + +/** this maps the enum to the text used at the command line */ +static const char *gc_modes[] = { + [GRAB_PEDANTIC] = "pedantic", + [GRAB_SLOPPY] = "sloppy", + [GRAB_AGGRESSIVE] = "aggressive", + NULL +}; + +/** grab clients that are not yet attached to a filter node */ +struct list_head inactive_grab_client_list; + +static int max_num_filters(void) +{ + int i, ret = 0; + for (i = 0; audio_formats[i]; i++) { + PARA_INFO_LOG("%s filter chain length: %d\n", audio_formats[i], + num_filters(i)); + ret = MAX(ret, num_filters(i)); + } + PARA_INFO_LOG("maximal filter chain length: %d\n", ret); + return ret; +} + +static int gc_write(char *buf, size_t len, struct filter_callback *fcb) +{ + struct grab_client *gc = fcb->data; + struct timeval tv = {0, 100}; + int ret; + +// PARA_INFO_LOG("writing %d bytes to fd %d\n", len, gc->fd); + fd_set wfds; + do { + FD_ZERO(&wfds); + FD_SET(gc->fd, &wfds); + ret = select(gc->fd + 1, NULL, &wfds, NULL, &tv); + } while (ret == EAGAIN || ret == EINTR); + if (ret != 1) { + if (gc->mode == GRAB_PEDANTIC) + return -E_PEDANTIC_GRAB; + if (gc->mode == GRAB_SLOPPY) + return 1; + } +rewrite: + ret = write(gc->fd, buf, len); + if (ret < 0) { + ret = -E_GC_WRITE; + gc->error = E_GC_WRITE; + } else { + if (ret != len) { + if (gc->mode == GRAB_PEDANTIC) + return -E_PEDANTIC_GRAB; + if (gc->mode == GRAB_AGGRESSIVE) { + len -= ret; + memmove(buf, buf + ret, len); + goto rewrite; + } + } + } + return ret; +} + +static int check_gc_args(struct grab_client *gc) +{ + int i; + struct grab_client_args_info *conf = gc->conf; + + PARA_INFO_LOG("filter_num: %d\n", gc->conf->filter_num_arg); + for (i = 0; gc_modes[i]; i++) + if (!strcmp(conf->mode_arg, gc_modes[i])) + break; + if (!gc_modes[i]) + return -E_INVALID_GRAB_MODE; + gc->mode = i; + if (conf->audio_format_given) { + gc->audio_format_num = get_audio_format_num(conf->audio_format_arg); + if (gc->audio_format_num < 0) + return gc->audio_format_num; + } + if (conf->slot_arg > MAX_STREAM_SLOTS) + return -E_BAD_GC_SLOT; + if (conf->filter_num_arg <= 0) + return -E_BAD_GC_FILTER_NUM; + if (conf->audio_format_given) { + if (num_filters(gc->audio_format_num) < conf->filter_num_arg) + return -E_BAD_GC_FILTER_NUM; + } else + if (conf->filter_num_arg > max_num_filters()) + return -E_BAD_GC_FILTER_NUM; + + return 1; +} + +static void add_inactive_gc(struct grab_client *gc) +{ + PARA_INFO_LOG("adding grab client %p (fd %d) to inactive list\n", + gc, gc->fd); + list_add(&gc->node, &inactive_grab_client_list); +} + +static void gc_free(struct grab_client *gc) +{ + int i; + + for (i = 0; i < gc->argc; i++) + free(gc->argv[i]); + free(gc->argv); + free(gc->conf); + free(gc); + +} + +static void gc_close(struct filter_callback *fcb) +{ + struct grab_client *gc = fcb->data; + + if (gc->conf->one_shot_given || gc->error) { + PARA_INFO_LOG("closing fd %d (grab client %p)\n", gc->fd, gc); + del_close_on_fork_list(gc->fd); + close(gc->fd); + gc_free(gc); + /* close on fork ?*/ + return; + } + add_inactive_gc(gc); +} + +/** + * move a grab client from the inactive list to a filter node + * + * \param gc the grab client to activate + * \param fn the filter node \a gc gets attached to + * + * \sa filter_node::callbacks, inactive_grab_client_list + */ +void activate_grab_client(struct grab_client *gc, struct filter_node *fn) +{ + PARA_INFO_LOG("activating %p (fd %d, filter node: %p)\n", gc, gc->fd, fn); + list_del(&gc->node); + list_add(&gc->fcb.node, &fn->callbacks); +} + +/** + * activate inactive grab clients if possible + * + * \param slot audiod's slot for the new audio file + * \param audio_format_num the number of the audio format of the new audio file + * \param filter_list the list of activated filters for that new audio file + * + * This is called from audiod.c when the current audio file changes. It loops + * over all inactive grab clients and checks each grab client's configuration + * to determine if the client in question wishes to grab the new stream. If + * yes, this grab client is moved from the inactive grab client list to an + * appropriate filter_node. + * + * \sa filter_chain_info::filters, inactive_grab_client_list, + * activate_grab_client + */ +void activate_inactive_grab_clients(int slot, int audio_format_num, + struct list_head *filter_list) +{ + struct grab_client *gc, *tmp; + int i; + struct filter_node *fn; + + list_for_each_entry_safe(gc, tmp, &inactive_grab_client_list, node) { +// PARA_INFO_LOG("checking inactive grab client %p\n", gc); + if (gc->conf->slot_arg >= 0 && gc->conf->slot_arg != slot) + continue; + if (gc->audio_format_num >= 0 && gc->audio_format_num != + audio_format_num) + continue; + if (gc->conf->filter_num_arg >= 0 && + num_filters(gc->audio_format_num) + < gc->conf->filter_num_arg) + continue; + i = 1; + list_for_each_entry(fn, filter_list, node) { + if (gc->conf->filter_num_arg <= 0 + || i == gc->conf->filter_num_arg) + break; + i++; + } + activate_grab_client(gc, fn); + } +} + +/** + * check the command line options and allocate a grab_client structure + * + * \param fd the file descriptor of the client + * \param argc the number of command line options + * \param argv pointers to the command line options + * \param err non-zero if an error occured + * + * If the command line options given by \a argc and \a argv are valid. + * allocate a struct grab_client and initialize it with this valid + * configuration. Moreover, add the new grab client to the inactive list. + * + * \return On success, this function returns a pointer to the newly created + * struct. On errors, it returns NULL and sets \a err appropriately. + * + * \sa grab_client, inactive_grab_client_list, activate_grab_client, + * filter_node::callbacks + */ +/* + * argc, argv get freed when com_grab() returns, so we have to make a + * copy. + */ +__malloc struct grab_client *grab_client_new(int fd, int argc, char **argv, int *err) +{ + int i, ret; + struct grab_client *gc = para_calloc(sizeof(struct grab_client)); + + gc->conf = para_calloc(sizeof(struct grab_client_args_info)); + gc->argc = argc; + gc->argv = para_calloc((argc + 1) * sizeof(char *)); + + for (i = 0; i < argc; i++) { + gc->argv[i] = para_strdup(argv[i]); + PARA_INFO_LOG("argc: %d, argv[%d]: %s\n", argc, i, gc->argv[i]); + } + PARA_INFO_LOG("argv[%d]: %s\n", argc, gc->argv[argc]); + ret = grab_client_cmdline_parser(gc->argc, gc->argv , gc->conf); + *err = -E_GC_SYNTAX; + if (ret) + goto err_out; + *err = -E_GC_HELP_GIVEN; + if (gc->conf->help_given) + goto err_out; + *err = check_gc_args(gc); + if (*err < 0) + goto err_out; + if (gc->conf->input_grab_given) { + gc->fcb.input_cb = gc_write; + gc->fcb.output_cb = NULL; + } else { + gc->fcb.output_cb = gc_write; + gc->fcb.input_cb = NULL; + } + gc->fd = fd; + gc->fcb.close = gc_close; + gc->fcb.data = gc; + add_inactive_gc(gc); + return gc; +err_out: + for (i = 0; i < argc; i++) + free(gc->argv[i]); + free(gc->argv); + free(gc->conf); + free(gc); + return NULL; +} + +/** initialize the grabbing subsystem. + * + * This has to be called once during startup before any other function from + * grab_client.c may be used. It initializes \a inactive_grab_client_list. + */ +void init_grabbing() +{ + PARA_INFO_LOG("%s", "grab init\n"); + INIT_LIST_HEAD(&inactive_grab_client_list); +} + diff --git a/grab_client.ggo b/grab_client.ggo new file mode 100644 index 00000000..3f87e001 --- /dev/null +++ b/grab_client.ggo @@ -0,0 +1,6 @@ +option "filter_num" f "point of filter chain to grab" int typestr="num" default="1" no +option "slot" s "only grab this slot; grab any slot if negative" int typestr="num" default="-1" no +option "mode" m "sloppy, pedantic, or aggressive" string typestr="grab_mode" default="sloppy" no +option "audio_format" a "only grab this type of input stream; grab any if empty" string typestr="name" default="" no +option "input_grab" i "grab the filter input instead of its output" flag off no +option "one_shot" o "stop grabbing if audio file changes" flag off no diff --git a/grab_client.h b/grab_client.h new file mode 100644 index 00000000..e66e9b24 --- /dev/null +++ b/grab_client.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2006 Andre Noll + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ + +/** \file grab_client.h exported symbols from grab_client.c */ + +#include "config.h" +/** + * handle blocking writes for the grab client fds + * + * - pedantic: close fd if write would block + * - sloppy: ignore the data and do not write + * - aggressive: write anyway (default) + * + */ +enum grab_mode {GRAB_PEDANTIC, GRAB_SLOPPY, GRAB_AGGRESSIVE}; + +/** describes one active grab client + * + * \sa filter_callback, filter_node::callbacks + */ +struct grab_client { +/** the file descriptor to send the grabbed stream to */ + int fd; +/** the command line options for this grab client */ + struct grab_client_args_info *conf; +/** pedantic, sloppy, or aggressive, computed from command line */ + enum grab_mode mode; +/** non-zero if the write() to \a fd failed */ + int error; +/** the number of the desired audio format, computed from command line */ + int audio_format_num; +/** the callback data which gets attached to a suitable filter_node */ + struct filter_callback fcb; +/** all grab clients belong either to a filter node or to the inactive list */ + struct list_head node; +/** the number of command line options */ + int argc; +/** pointers to the command line options */ + char **argv; +}; + +__malloc struct grab_client *grab_client_new(int fd, int argc, char **argv, int *err); +void activate_inactive_grab_clients(int slot, int audio_format_num, struct list_head *filter_list); +void activate_grab_client(struct grab_client *gc, struct filter_node *fn); +void init_grabbing(void); diff --git a/gui.c b/gui.c new file mode 100644 index 00000000..351748e4 --- /dev/null +++ b/gui.c @@ -0,0 +1,1364 @@ +/* + * Copyright (C) 1998-2006 Andre Noll + * + * 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 +#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 = "", + .name = "scroll_up", + .description = "scroll up one line", + .handler = com_scroll_up + }, { + .key = "", + .name = "scroll_down", + .description = "scroll down one line", + .handler = com_scroll_down + }, { + .key = "", + .name = "page_up", + .description = "scroll up one page", + .handler = com_page_up + }, { + .key = "", + .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, ""); + return buf; + } + if (c == KEY_DOWN) { + sprintf(buf, ""); + return buf; + } + if (c == KEY_LEFT) { + sprintf(buf, ""); + return buf; + } + if (c == KEY_RIGHT) { + sprintf(buf, ""); + return buf; + } + if (c == KEY_NPAGE) { + sprintf(buf, ""); + return buf; + } + if (c == KEY_PPAGE) { + sprintf(buf, ""); + 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, "", c - KEY_F0); + else if (isprint(c)) + snprintf(buf, sizeof(buf), "%c", (unsigned char) c); + else + snprintf(buf, sizeof(buf), "\\x%hx", (unsigned short) c); + return buf; +} + +static char *configfile_exists(void) +{ + static char *config_file; + char *tmp; + + if (!conf.config_file_given) { + if (!config_file) { + char *home = para_homedir(); + config_file = make_message("%s/.paraslash/gui.conf", + home); + free(home); + } + tmp = config_file; + } else + tmp = conf.config_file_arg; + return file_exists(tmp)? tmp: NULL; +} + +/* + * print num spaces to curses window + */ +static void add_spaces(WINDOW* win, unsigned int num) +{ + while (num > 0) { + num--; + waddstr(win, " "); + } +} + +/* + * print aligned string to curses window. This function always prints + * exactly len chars. + */ +static int align_str(WINDOW* win, char *string, unsigned int len, + unsigned int align) +{ + int num; /* of spaces */ + char *str; + + if (!win || !string) + return -1; + num = len - strlen(string); + str = para_strdup(string); + if (num < 0) { + str[len] = '\0'; + num = 0; + } + if (align == LEFT) { + waddstr(win, str); + add_spaces(win, num); + } else if (align == RIGHT) { + add_spaces(win, num); + waddstr(win, str); + } else { + add_spaces(win, num / 2); + waddstr(win, str[0]? str: ""); + add_spaces(win, num - num / 2); + } + free(str); + return 1; +} + +__printf_2_3 static void print_in_bar(int color, char *fmt,...) +{ + char *msg; + + if (!curses_active) + return; + wattron(in.win, COLOR_PAIR(color)); + PARA_VSPRINTF(fmt, msg); + wmove(in.win, 0, 0); + align_str(in.win, msg, sb.cols, LEFT); + free(msg); + wrefresh(in.win); +} + +/* + * update the status bar + */ +static void print_status_bar(void) +{ + if (!curses_active) + return; + wmove(sb.win, 0, 0); + align_str(sb.win,STANDARD_STATUS_BAR, sb.cols, CENTER); + wrefresh(sb.win); +} + +/* + * get the number of the oldest rbe that is (partially) visible. On return, + * lines contains the sum of the number of lines of all visable entries. If the + * first one is only partially visible, lines is greater than bot.lines. + */ +static int first_visible_rbe(unsigned *lines) +{ + int i; + *lines = 0; + for (i = scroll_position; i < RINGBUFFER_SIZE; i++) { + struct rb_entry *rbe = ringbuffer_get(bot_win_rb, i); + int rbe_lines; + if (!rbe) + return i - 1; +// fprintf(stderr, "found: %s\n", rbe->msg); + rbe_lines = NUM_LINES(rbe->len); + if (rbe_lines > bot.lines) + return -1; + *lines += rbe_lines; + if (*lines >= bot.lines) + return i; + } + return RINGBUFFER_SIZE - 1; +} + +static int draw_top_rbe(unsigned *lines) +{ + unsigned len; + int offset, fvr = first_visible_rbe(lines); + struct rb_entry *rbe; + + if (fvr < 0) + return -1; + wmove(bot.win, 0, 0); + rbe = ringbuffer_get(bot_win_rb, fvr); + if (!rbe) + return -1; + /* first rbe might be only partially visible */ + offset = (*lines - bot.lines) * bot.cols; + len = strlen(rbe->msg); + if (offset < 0 || len < offset) + return -1; + wattron(bot.win, COLOR_PAIR(rbe->color)); + waddstr(bot.win, rbe->msg + offset); + *lines = NUM_LINES(len - offset); + return fvr; +} + +static void redraw_bot_win(void) +{ + unsigned lines; + int i; + + wmove(bot.win, 0, 0); + wclear(bot.win); + i = draw_top_rbe(&lines); + if (i <= 0) + goto out; + while (i > 0 && lines < bot.lines) { + struct rb_entry *rbe = ringbuffer_get(bot_win_rb, --i); + if (!rbe) { + lines++; + waddstr(bot.win, "\n"); + continue; + } + lines += NUM_LINES(rbe->len); + wattron(bot.win, COLOR_PAIR(rbe->color)); + waddstr(bot.win, "\n"); + waddstr(bot.win, rbe->msg); + } +out: + wrefresh(bot.win); +} + +static void rb_add_entry(int color, char *msg) +{ + struct rb_entry *old, *new = para_malloc(sizeof(struct rb_entry)); + int x, y; + new->color = color; + new->len = strlen(msg); + new->msg = msg; + old = ringbuffer_add(bot_win_rb, new); +// fprintf(stderr, "added: %s\n", new->msg); + if (old) { + free(old->msg); + free(old); + } + if (scroll_position) { + /* discard current scrolling, like xterm does */ + scroll_position = 0; + redraw_bot_win(); + return; + } + wattron(bot.win, COLOR_PAIR(color)); + getyx(bot.win, y, x); + if (y || x) + waddstr(bot.win, "\n"); + waddstr(bot.win, msg); +} + +/* + * print formated output to bot win and refresh + */ +__printf_2_3 static void outputf(int color, char* fmt,...) +{ + char *msg; + + if (!curses_active) + return; + PARA_VSPRINTF(fmt, msg); + rb_add_entry(color, msg); + wrefresh(bot.win); +} + +static void add_output_line(char *line) +{ + if (!curses_active) + return; + rb_add_entry(COLOR_OUTPUT, para_strdup(line)); +} + +void para_log(int ll, char *fmt,...) +{ + int color; + char *msg; + + if (ll < conf.loglevel_arg || !curses_active) + return; + switch (ll) { + case DEBUG: + case INFO: + case NOTICE: + color = COLOR_MSG; + break; + default: + color = COLOR_ERRMSG; + } + PARA_VSPRINTF(fmt, msg); + chop(msg); + rb_add_entry(color, msg); + wrefresh(bot.win); +} + +static void setup_signal_handling(void) +{ + signal_pipe = para_signal_init(); + para_install_sighandler(SIGINT); + para_install_sighandler(SIGTERM); + para_install_sighandler(SIGCHLD); + para_install_sighandler(SIGWINCH); + para_install_sighandler(SIGUSR1); + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); +} + +static void do_exit(int ret) +{ + signal(SIGTERM, SIG_IGN); + kill(0, SIGTERM); + exit(ret); +} + +static void shutdown_curses(void) +{ + if (!curses_active) + return; + def_prog_mode(); + curses_active = 0; + endwin(); +} + +static void finish(int ret) +{ + shutdown_curses(); + do_exit(ret); +} + +/* + * exit curses and print given message to stdout/stderr + */ +__printf_2_3 static void msg_n_exit(int ret, char* fmt, ...) +{ + va_list argp; + FILE *outfd = ret? stderr: stdout; + + shutdown_curses(); + va_start(argp, fmt); + vfprintf(outfd, fmt, argp); + va_end(argp); + do_exit(ret); +} + +static void print_welcome(void) +{ + int ll = conf.loglevel_arg; + if (ll > NOTICE) + return; + outputf(COLOR_WELCOME, "Welcome to para_gui " VERSION + " \"" CODENAME "\". Theme: %s", theme.name); + wclrtoeol(bot.win); +} + +/* + * init all windows + */ +static void init_wins(int top_lines) +{ + int i; + + top.lines = top_lines; + top.cols = COLS; + top.begy = 0; + top.begx = 0; + + bot.lines = LINES - top.lines - 3; + bot.cols = COLS; + bot.begy = top.lines + 1; + bot.begx = 0; + + sb.lines = 1; + sb.cols = COLS; + sb.begy = LINES - 2; + sb.begx = 0; + + in.lines = 1; + in.cols = COLS; + in.begy = LINES - 1; + in.begx = 0; + + sep.lines = 1; + sep.cols = COLS; + sep.begy = top.lines; + sep.begx = 0; + + assume_default_colors(theme.default_fg, theme.default_bg); + if (top.win) { + mvwin(top.win, top.begy, top.begx); + wresize(top.win, top.lines, top.cols); + + mvwin(sb.win, sb.begy, sb.begx); + wresize(sb.win, sb.lines, sb.cols); + + mvwin(sep.win, sep.begy, sep.begx); + wresize(sep.win, sep.lines, sep.cols); + + mvwin(bot.win, bot.begy, bot.begx); + wresize(bot.win, bot.lines, bot.cols); + + mvwin(in.win, in.begy, in.begx); + wresize(in.win, in.lines, in.cols); + } else { + sep.win = newwin(sep.lines, sep.cols, sep.begy, sep.begx); + top.win = newwin(top.lines, top.cols, top.begy, top.begx); + bot.win = newwin(bot.lines, bot.cols, bot.begy, bot.begx); + sb.win = newwin(sb.lines, sb.cols, sb.begy, sb.begx); + in.win = newwin(in.lines, in.cols, in.begy, in.begx); + if (!top.win || !bot.win || !sb.win || !in.win || !sep.win) + msg_n_exit(1, "Error: Cannot create curses windows\n"); + wclear(bot.win); + wclear(sb.win); + wclear(in.win); + scrollok(bot.win, 1); + wattron(sb.win, COLOR_PAIR(COLOR_STATUSBAR)); + wattron(sep.win, COLOR_PAIR(COLOR_SEPARATOR)); + wattron(bot.win, COLOR_PAIR(COLOR_BOT)); + wattron(top.win, COLOR_PAIR(COLOR_TOP)); + nodelay(top.win, 1); + nodelay(bot.win, 1); + nodelay(sb.win, 1); + nodelay(in.win, 0); + + keypad(top.win, 1); + keypad(bot.win, 1); + keypad(sb.win, 1); + keypad(in.win, 1); + print_status_bar(); + } + wmove(sep.win, 0, 0); + for (i = 1; i <= COLS; i++) + waddstr(sep.win, theme.sep_str); + wclear(top.win); + //wclear(bot.win); + wnoutrefresh(top.win); + wnoutrefresh(bot.win); + //wnoutrefresh(sb.win); + print_status_bar(); + wnoutrefresh(in.win); + wnoutrefresh(sep.win); + doupdate(); +} + +static void check_geometry(void) +{ + if (LINES < theme.lines_min || COLS < theme.cols_min) + msg_n_exit(EXIT_FAILURE, "Error: Terminal (%dx%d) too small" + " (need at least %dx%d)\n", COLS, LINES, + theme.cols_min, theme.lines_min); +} + +/* + * Print stat item #i to curses window + */ +static void print_stat_item(int i) +{ + char *tmp; + struct stat_item_data d = theme.data[i]; + char *c = stat_content[i]; + + if (!curses_active || !d.len || !c) + return; + tmp = make_message("%s%s%s", d.prefix, c, d.postfix); +// PARA_DEBUG_LOG("%s: read: %s\n", __func__, tmp); + wmove(top.win, d.y * top.lines / 100, d.x * COLS / 100); + wrefresh(top.win); + wattron(top.win, COLOR_PAIR(i + 1)); + align_str(top.win, tmp, d.len * COLS / 100, d.align); + free(tmp); + wrefresh(top.win); +} + +static void print_all_items(void) +{ + int i; + + if (!curses_active) + return; + for (i = 0; i < NUM_STAT_ITEMS; i++) + print_stat_item(i); +} +static void clear_all_items(void) +{ + int i; + + for (i = 0; i < NUM_STAT_ITEMS; i++) { + free(stat_content[i]); + stat_content[i] = para_strdup(""); + } +} + +static void init_colors(void) +{ + int i; + if (!has_colors()) + msg_n_exit(EXIT_FAILURE, "Error: No color term\n"); + start_color(); + for (i = 0; i < NUM_STAT_ITEMS; i++) + if (theme.data[i].len) + init_pair(i + 1, theme.data[i].fg, theme.data[i].bg); + init_pair(COLOR_STATUSBAR, theme.sb_fg, theme.sb_bg); + init_pair(COLOR_COMMAND, theme.cmd_fg, theme.cmd_bg); + init_pair(COLOR_OUTPUT, theme.output_fg, theme.output_bg); + init_pair(COLOR_MSG, theme.msg_fg, theme.msg_bg); + init_pair(COLOR_ERRMSG, theme.err_msg_fg, theme.err_msg_bg); + init_pair(COLOR_WELCOME, theme.welcome_fg, theme.welcome_bg); + init_pair(COLOR_SEPARATOR, theme.sep_fg, theme.sep_bg); + init_pair(COLOR_TOP, theme.default_fg, theme.default_bg); + init_pair(COLOR_BOT, theme.default_fg, theme.default_bg); +} + +/* + * (re-)initialize the curses library FIXME: Error checking + */ +static void init_curses(void) +{ + curses_active = 1; + if (top.win && refresh() == ERR) { /* refesh is really needed */ + msg_n_exit(EXIT_FAILURE, "refresh() failed\n"); + } + check_geometry(); + curs_set(0); /* make cursor invisible, ignore errors */ +// if (noraw() == ERR); +// msg_n_exit(EXIT_FAILURE, "can not place terminal out of " +// "raw mode\n"); + nonl(); /* tell curses not to do NL->CR/NL on output */ + noecho(); /* don't echo input */ + cbreak(); /* take input chars one at a time, no wait for \n */ + //reset_prog_mode(); + init_colors(); + clear(); + init_wins(theme.top_lines_default); + print_all_items(); + noecho(); /* don't echo input */ +} + + +static void check_sigchld(void) +{ + pid_t pid; +reap_next_child: + pid = para_reap_child(); + if (pid <= 0) + return; + if (pid == external_cmd_pid) { + external_cmd_pid = 0; + external_cmd_died = 1; + } + goto reap_next_child; +} + +/* + * print status line if line starts with known command. + */ +static void check_stat_line(char *line) +{ + int i; + +// PARA_INFO_LOG("%s: checking: %s\n", __func__, line); + i = stat_line_valid(line); + if (i >= 0) { + line += strlen(status_item_list[i]) + 1; + free(stat_content[i]); + stat_content[i] = para_strdup(line); + print_stat_item(i); + } + return; +} + +/* + * This sucker modifies its first argument. *handler and *arg are + * pointers to 0-terminated strings (inside line). Crap. + */ +static int split_key_map(char *line, char **handler, char **arg) +{ + if (!(*handler = strchr(line + 1, ':'))) + goto err_out; + **handler = '\0'; + (*handler)++; + if (!(*arg = strchr(*handler, ':'))) + goto err_out; + **arg = '\0'; + (*arg)++; + return 1; +err_out: + return 0; +} + +static int check_key_map_args(void) +{ + char *s; + int i, ret = -1; + char *tmp = NULL, *handler, *arg; + + for (i = 0; i < conf.key_map_given; ++i) { + s = conf.key_map_arg[i]; + if (!(*s)) + goto err_out; + free(tmp); + tmp = para_strdup(s); + if (!split_key_map(tmp, &handler, &arg)) + goto err_out; + if (strlen(handler) != 1) + goto err_out; + if (*handler != 'x' + && *handler != 'd' + && *handler != 'i' + && *handler != 'p') + goto err_out; + if (*handler != 'i') + continue; + if (find_cmd_byname(arg) < 0) + goto err_out; + } + ret = 0; +err_out: + free(tmp); + return ret; +} + +/* + * React to various signal-related events + */ +static void handle_signal(int sig) +{ + switch (sig) { + case SIGTERM: + msg_n_exit(EXIT_FAILURE, + "only the good die young (caught SIGTERM))\n"); + return; + case SIGWINCH: + if (curses_active) { + shutdown_curses(); + init_curses(); + redraw_bot_win(); + } + return; + case SIGINT: + PARA_WARNING_LOG("%s", "caught SIGINT, reset"); + /* Nothing to do. SIGINT killed our child, para_client stat. + * This get noticed by do_select which resets everything + */ + return; + case SIGUSR1: + PARA_NOTICE_LOG("%s", "got SIGUSR1, rereading configuration"); + com_reread_conf(); + return; + case SIGCHLD: + check_sigchld(); + return; + } +} + +/* open pipe if last attempt to open was more than 2 secs ago */ +static int open_audiod_pipe(void) +{ + static time_t open_time, *p; + time_t now; + double diff; + + if (p) { + time(&now); + diff = difftime(now, open_time); + if (diff < 2) + return -1; + } else + p = &open_time; + time(&open_time); + return para_open_audiod_pipe(conf.stat_cmd_arg); +} + + +/* + * This is the core select loop. Besides the (internal) signal + * pipe, the following other fds are checked according to the mode: + * + * GETCH_MODE: check stdin, return when key is pressed + * + * COMMAND_MODE: check command_pipe and stdin. Return when a screen full + * of output has been read or when other end has closed command pipe or + * when any key is pressed. + * + * EXTERNAL_MODE: Check only signal pipe. Used when an external command + * is running. During that thime curses is disabled. Returns when + * external_cmd_pid == 0. + */ +static int do_select(int mode) +{ + fd_set rfds; + int ret; + int max_fileno, cp_numread = 1; + char command_buf[STRINGSIZE] = ""; + int cbo = 0; /* command buf offset */ + struct timeval tv; +repeat: + tv.tv_sec = conf.timeout_arg / 1000; + tv.tv_usec = (conf.timeout_arg % 1000) * 1000; +// ret = refresh_status(); + FD_ZERO(&rfds); + max_fileno = 0; + /* audiod pipe */ + if (audiod_pipe < 0) + audiod_pipe = open_audiod_pipe(); + if (audiod_pipe >= 0) { + FD_SET(audiod_pipe, &rfds); + max_fileno = MAX(max_fileno, audiod_pipe); + } + + /* signal pipe */ + FD_SET(signal_pipe, &rfds); + max_fileno = MAX(max_fileno, signal_pipe); + /* command pipe only for COMMAND_MODE */ + if (command_pipe >= 0 && mode == COMMAND_MODE) { + FD_SET(command_pipe, &rfds); + max_fileno = MAX(max_fileno, command_pipe); + } + FD_SET(STDIN_FILENO, &rfds); + ret = select(max_fileno + 1, &rfds, NULL, NULL, &tv); +// PARA_DEBUG_LOG("select returned %d\n", ret); + + /* signals */ + if (FD_ISSET(signal_pipe, &rfds)) { + int sig_nr = para_next_signal(); + if (sig_nr > 0) + handle_signal(sig_nr); + } + if (ret <= 0) + goto check_return; /* skip fd checks */ + /* read command pipe if ready */ + if (command_pipe >= 0 && mode == COMMAND_MODE && + FD_ISSET(command_pipe, &rfds)) { + cp_numread = read(command_pipe, command_buf + cbo, + STRINGSIZE - 1 - cbo); + if (cp_numread >= 0) + cbo += cp_numread; + else { + if (cp_numread < 0) + PARA_ERROR_LOG("read error (%d)", cp_numread); + close(command_pipe); + command_pipe = -1; + } + } + if (audiod_pipe >= 0 && FD_ISSET(audiod_pipe, &rfds)) + if (read_audiod_pipe(audiod_pipe, check_stat_line) <= 0) { + close(audiod_pipe); + audiod_pipe = -1; + clear_all_items(); + free(stat_content[SI_STATUS_BAR]); + stat_content[SI_STATUS_BAR] = + para_strdup("audiod not running!?\n"); + print_all_items(); + sleep(1); + } +check_return: + switch (mode) { + case COMMAND_MODE: + if (cp_numread <= 0 && !cbo) /* command complete */ + return 0; + if (cbo) + cbo = for_each_line(command_buf, cbo, &add_output_line, 0); + if (cp_numread <= 0) + cbo = 0; + wrefresh(bot.win); + break; + case GETCH_MODE: + ret = wgetch(top.win); + if (ret != ERR && ret != KEY_RESIZE) + return ret; + break; + case EXTERNAL_MODE: + if (external_cmd_died) { + external_cmd_died = 0; + return 0; + } + } + goto repeat; +} + +/* + * read from command pipe and print data to bot window + */ +static int send_output(void) +{ + if (command_pipe < 0) + return 0; + if (do_select(COMMAND_MODE) >= 0) + PARA_INFO_LOG("%s", "command complete"); + else + PARA_NOTICE_LOG("%s", "command aborted"); + print_in_bar(COLOR_MSG, " "); + return 1; +} + +static int client_cmd_cmdline(char *cmd) +{ + pid_t pid; + int ret, fds[3] = {0, 1, 0}; + char *c = make_message(BINDIR "/para_client %s", cmd); + + outputf(COLOR_COMMAND, "%s", c); + print_in_bar(COLOR_MSG, "executing client command, hit q to abort\n"); + ret = para_exec_cmdline_pid(&pid, c, fds); + free(c); + if (ret < 0) + return -1; + command_pipe = fds[1]; + return send_output(); +} + +/* + * exec command and print output to bot win + */ +static int display_cmd(char *cmd) +{ + pid_t pid; + int fds[3] = {0, 1, 0}; + + print_in_bar(COLOR_MSG, "executing display command, hit q to abort"); + outputf(COLOR_COMMAND, "%s", cmd); + if (para_exec_cmdline_pid(&pid, cmd, fds) < 0) + return -1; + command_pipe = fds[1]; + return send_output(); +} + +/* + * shutdown curses and stat pipe before executing external commands + */ +static int external_cmd(char *cmd) +{ + int fds[3] = {-1, -1, -1}; + + if (external_cmd_pid) + return -1; + shutdown_curses(); + para_exec_cmdline_pid(&external_cmd_pid, cmd, fds); + do_select(EXTERNAL_MODE); + init_curses(); + return 0; +} + +static void print_scroll_msg(void) +{ + unsigned lines_total, filled = ringbuffer_filled(bot_win_rb); + int first_rbe = first_visible_rbe(&lines_total); + print_in_bar(COLOR_MSG, "scrolled view: %d-%d/%d\n", filled - first_rbe, + filled - scroll_position, ringbuffer_filled(bot_win_rb)); +} + +static void com_page_down(void) +{ + unsigned lines = 0; + int i = scroll_position; + while (lines < bot.lines && --i > 0) { + struct rb_entry *rbe = ringbuffer_get(bot_win_rb, i); + if (!rbe) + break; + lines += NUM_LINES(strlen(rbe->msg)); + } + if (lines) { + scroll_position = i; + redraw_bot_win(); + print_scroll_msg(); + return; + } + print_in_bar(COLOR_ERRMSG, "bottom of buffer is shown\n"); +} + +static void com_page_up(void) +{ + unsigned lines; + int fvr = first_visible_rbe(&lines); + if (fvr < 0 || fvr + 1 >= ringbuffer_filled(bot_win_rb)) { + print_in_bar(COLOR_ERRMSG, "top of buffer is shown\n"); + return; + } + scroll_position = fvr + 1; + for (; scroll_position > 0; scroll_position--) { + fvr = first_visible_rbe(&lines); + if (lines == bot.lines) + break; + } + redraw_bot_win(); + print_scroll_msg(); +} + +static void com_scroll_down(void) +{ + struct rb_entry *rbe; + int rbe_lines; + + if (!scroll_position) { + print_in_bar(COLOR_ERRMSG, "bottom of buffer is shown\n"); + return; + } + scroll_position--; + rbe = ringbuffer_get(bot_win_rb, scroll_position); + rbe_lines = NUM_LINES(rbe->len); + wscrl(bot.win, rbe_lines); + wmove(bot.win, bot.lines - rbe_lines, 0); + wattron(bot.win, COLOR_PAIR(rbe->color)); + waddstr(bot.win, rbe->msg); + wrefresh(bot.win); + print_scroll_msg(); +} + +static void com_scroll_up(void) +{ + struct rb_entry *rbe = NULL; + unsigned lines; + int i, first_rbe, scroll; + + /* the entry that is going to vanish */ + rbe = ringbuffer_get(bot_win_rb, scroll_position); + if (!rbe) + goto err_out; + scroll = NUM_LINES(rbe->len); + first_rbe = first_visible_rbe(&lines); + if (first_rbe < 0 || (first_rbe == ringbuffer_filled(bot_win_rb) - 1)) + goto err_out; + scroll_position++; + wscrl(bot.win, -scroll); + i = draw_top_rbe(&lines); + if (i < 0) + goto err_out; + while (i > 0 && lines < scroll) { + int rbe_lines; + rbe = ringbuffer_get(bot_win_rb, --i); + if (!rbe) + break; + rbe_lines = NUM_LINES(rbe->len); + lines += rbe_lines; +// fprintf(stderr, "msg: %s\n", rbe->msg); + wattron(bot.win, COLOR_PAIR(rbe->color)); + waddstr(bot.win, "\n"); + waddstr(bot.win, rbe->msg); + if (!i) + break; + i--; + } + wrefresh(bot.win); + print_scroll_msg(); + return; +err_out: + print_in_bar(COLOR_ERRMSG, "top of buffer is shown\n"); +} + +static void com_ll_decr(void) +{ + if (conf.loglevel_arg <= DEBUG) { + print_in_bar(COLOR_ERRMSG, + "loglevel already at maximal verbosity\n"); + return; + } + conf.loglevel_arg--; + print_in_bar(COLOR_MSG, "loglevel set to %d\n", conf.loglevel_arg); +} + +static void com_ll_incr(void) +{ + if (conf.loglevel_arg >= EMERG) { + print_in_bar(COLOR_ERRMSG, + "loglevel already at miminal verbosity\n"); + return; + } + conf.loglevel_arg++; + print_in_bar(COLOR_MSG, "loglevel set to %d\n", conf.loglevel_arg); +} + +/* + * reread configuration, terminate on errors + */ +static void com_reread_conf(void) +{ + char *cf =configfile_exists(); + + if (!cf) { + PARA_WARNING_LOG("%s", "there is no configuration to read"); + return; + } + PARA_INFO_LOG("%s", "rereading command line options and config file"); + cmdline_parser(_argc, _argv, &conf); + cmdline_parser_configfile(cf, &conf, 1, 1, 0); + PARA_NOTICE_LOG("%s", "configuration read"); + if (check_key_map_args() < 0) + finish(EXIT_FAILURE); +} + +static void com_help(void) +{ + int i; + + for (i = 0; i < conf.key_map_given; ++i) { + char *s = conf.key_map_arg[i], *handler, *arg, + *desc = "", tmp[MAXLINE]; + + strcpy(tmp, s); + if (!split_key_map(tmp, &handler, &arg)) + return; + switch(*handler) { + case 'i': + handler = "internal"; + desc = command_list[find_cmd_byname(arg)].description; + break; + case 'x': handler = "external"; break; + case 'd': handler = "display "; break; + case 'p': handler = "para "; break; + } + outputf(COLOR_MSG, "%s\t%s\t%s%s\t%s", tmp, handler, arg, + strlen(arg) < 8? "\t" : "", + desc); + } + for (i = 0; command_list[i].handler; i++) { + struct gui_command gc = command_list[i]; + + outputf(COLOR_MSG, "%s\tinternal\t%s\t%s%s", gc.key, gc.name, + strlen(gc.name) < 8? "\t" : "", + gc.description); + } + print_in_bar(COLOR_MSG, "try \"para_gui -h\" or \"para_client help\" " + "for more info"); + return; +} + +static void com_shrink_top_win(void) +{ + if (top.lines <= theme.top_lines_min) { + PARA_WARNING_LOG("%s", "can not decrease top window"); + return; + } + init_wins(top.lines - 1); + wclear(top.win); + print_all_items(); + print_in_bar(COLOR_MSG, "%s", "decreased top window"); +} + +static void com_enlarge_top_win(void) +{ + if (bot.lines < 3) { + PARA_WARNING_LOG("%s", "can not increase top window"); + return; + } + init_wins(top.lines + 1); + wclear(top.win); + print_all_items(); + print_in_bar(COLOR_MSG, "increased top window"); +} + +static void com_version(void) +{ + print_in_bar(COLOR_MSG, "para_gui " VERSION " \"" CODENAME "\""); +} + +static void com_quit(void) +{ + finish(0); +} + +static void com_refresh(void) +{ + shutdown_curses(); + init_curses(); +} + +static void change_theme(int next) +{ + if (next) + next_theme(&theme); + else + prev_theme(&theme); + /* This seems to be needed twice, why? */ + com_refresh(); + com_refresh(); + PARA_NOTICE_LOG("new theme: %s", theme.name); +} + +static void com_next_theme(void) +{ + change_theme(1); +} + +static void com_prev_theme(void) +{ + change_theme(0); +} + + +static void handle_command(int c) +{ + int i; + + /* first check user's key bindings */ + for (i = 0; i < conf.key_map_given; ++i) { + char tmp[MAXLINE], *handler, *arg; + + strcpy(tmp, conf.key_map_arg[i]); + if (!split_key_map(tmp, &handler, &arg)) + return; + if (!strcmp(tmp, km_keyname(c))) { + if (*handler == 'd') { + display_cmd(arg); + return; + } + if (*handler == 'x') { + external_cmd(arg); + return; + } + if (*handler == 'p') { + client_cmd_cmdline(arg); + return; + } + if (*handler == 'i') { + int num = find_cmd_byname(arg); + if (num >= 0) + command_list[num].handler(); + return; + } + } + } + /* not found, check internal key bindings */ + for (i = 0; command_list[i].handler; i++) { + if (!strcmp(km_keyname(c), command_list[i].key)) { + command_list[i].handler(); + return; + } + } + print_in_bar(COLOR_ERRMSG, "key '%s' is not bound, press ? for help", + km_keyname(c)); +} + +int main(int argc, char *argv[]) +{ + int ret; + char *cf; + + _argc = argc; + _argv = argv; + + if (cmdline_parser(argc, argv, &conf)) { + fprintf(stderr, "parse error while reading command line\n"); + exit(EXIT_FAILURE); + } + init_theme(0, &theme); + top.lines = theme.top_lines_default; + if (check_key_map_args() < 0) { + fprintf(stderr, "invalid key map\n"); + exit(EXIT_FAILURE); + } + cf = configfile_exists(); + if (!cf && conf.config_file_given) { + fprintf(stderr, "can not read config file %s\n", + conf.config_file_arg); + exit(EXIT_FAILURE); + } + if (cf) + cmdline_parser_configfile(cf, &conf, 0, 0, 0); + if (check_key_map_args() < 0) { + fprintf(stderr, "invalid key map in config file\n"); + exit(EXIT_FAILURE); + } + setup_signal_handling(); + bot_win_rb = ringbuffer_new(RINGBUFFER_SIZE); + initscr(); /* needed only once, always successful */ + init_curses(); + print_welcome(); + for (;;) { + print_status_bar(); + ret = do_select(GETCH_MODE); + if (!ret) + continue; + print_in_bar(COLOR_MSG, " "); + handle_command(ret); + } +} diff --git a/gui.conf.sample b/gui.conf.sample new file mode 100644 index 00000000..6c4873b0 --- /dev/null +++ b/gui.conf.sample @@ -0,0 +1,42 @@ +# modify to suit your needs + +# jump around in current song with F1-F10 +key_map "^:p:jmp 0" +key_map ":p:jmp 10" +key_map ":p:jmp 20" +key_map ":p:jmp 30" +key_map ":p:jmp 40" +key_map ":p:jmp 50" +key_map ":p:jmp 60" +key_map ":p:jmp 70" +key_map ":p:jmp 80" +key_map ":p:jmp 90" +key_map ":p:jmp 97" + +#key mappings for standard options +key_map "s:p:play" +key_map "$:p:next" +key_map "p:p:pause" +key_map " :p:stop" + +# jump forward and backward in current song with j,k,J,K +key_map "k:p:ff 10" +key_map "j:p:ff 10-" +key_map "K:p:ff 60" +key_map "J:p:ff 60-" + +# misc stuff +key_map "l:p:last" +key_map "u:p:uptime" +key_map "A:p:laa" +key_map "S:p:streams" + +# shell escapes +key_map "!:x:/bin/bash" +key_map "::x:para_client" + +# display commands +# key_map "Q:d:aumix -q" + +# do something like this to control volume +#key_map "5:d:aumix -v 50" diff --git a/gui.ggo b/gui.ggo new file mode 100644 index 00000000..2f5dfc26 --- /dev/null +++ b/gui.ggo @@ -0,0 +1,9 @@ +section "general options" +option "auto_decode" a "auto-decode audio stream" flag on +option "config_file" c "(default='~/.paraslash/gui.conf')" string typestr="filename" no +option "loglevel" l "set loglevel (0-6)" int typestr="level" default="4" no +option "timeout" t "set timeout" int typestr="milliseconds" default="300" no +option "stat_cmd" s "command to read server and audiod status data from" string typestr="command" default="para_audioc -t 100 stat" no + +section "mapping keys to commands" +option "key_map" k "Map key k to command c using mode m. Mode may be d, x or p for display, external and paraslash commands, respectively. Of course, this option may be given multiple times, one for each key mapping." string typestr="k:m:c" no multiple diff --git a/gui_common.c b/gui_common.c new file mode 100644 index 00000000..2c499126 --- /dev/null +++ b/gui_common.c @@ -0,0 +1,29 @@ +#include "para.h" + +extern const char *status_item_list[NUM_STAT_ITEMS]; + + +int para_open_audiod_pipe(char *cmd) +{ + int fds[3] = {0, 1, 0}; + pid_t pid; + return para_exec_cmdline_pid(&pid, cmd, fds) > 0? + fds[1] : -1; +} + +int read_audiod_pipe(int fd, void (*line_handler)(char *) ) +{ + static char buf[STRINGSIZE + 1]; + static ssize_t loaded, bufsize = sizeof(buf); + ssize_t ret; + + if (loaded >= bufsize) + loaded = 0; + ret = read(fd, buf + loaded, bufsize - loaded); + if (ret > 0) { + loaded += ret; + buf[loaded] = '\0'; + loaded = for_each_line(buf, loaded, line_handler, 0); + } + return ret; +} diff --git a/gui_theme.c b/gui_theme.c new file mode 100644 index 00000000..87d8e050 --- /dev/null +++ b/gui_theme.c @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2005 Andre Noll + * + * 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 + +#define NUM_THEMES 2 + + +static int current_theme_num; + +static void init_theme_simple(struct gui_theme *t) +{ + struct stat_item_data *d = t->data; + t->name = "simple"; + t->author = "Andre Noll"; + t->lines_min = 5; + t->top_lines_min = 2; + t->cols_min = 40; + t->top_lines_default = 2; + t->sb_bg = COLOR_CYAN; + t->sb_fg = COLOR_BLACK; + t->cmd_bg = COLOR_WHITE; + t->cmd_fg = COLOR_BLACK; + t->output_bg = COLOR_BLUE; + t->output_fg = COLOR_WHITE; + t->msg_bg = COLOR_BLUE; + t->msg_fg = COLOR_YELLOW; + t->err_msg_bg = COLOR_RED; + t->err_msg_fg = COLOR_WHITE; + t->welcome_bg = COLOR_BLUE; + t->welcome_fg = COLOR_WHITE; + t->sep_bg = COLOR_BLUE; + t->sep_fg = COLOR_CYAN; + t->default_fg = COLOR_WHITE; + t->default_bg = COLOR_BLUE; + t->sep_str = "*"; + + d[SI_STATUS_BAR].prefix = ""; + d[SI_STATUS_BAR].postfix = ""; + d[SI_STATUS_BAR].fg = COLOR_WHITE; + d[SI_STATUS_BAR].bg = COLOR_BLUE; + d[SI_STATUS_BAR].align = CENTER; + d[SI_STATUS_BAR].x = 0; + d[SI_STATUS_BAR].y = 7; + d[SI_STATUS_BAR].len = 100; + + d[SI_STATUS].prefix = "para_server: "; + d[SI_STATUS].postfix = ""; + d[SI_STATUS].fg = COLOR_WHITE; + d[SI_STATUS].bg = COLOR_BLUE; + d[SI_STATUS].align = CENTER; + d[SI_STATUS].x = 0; + d[SI_STATUS].y = 60; + d[SI_STATUS].len = 50; + + d[SI_AUDIOD_STATUS].prefix = "para_audiod: "; + d[SI_AUDIOD_STATUS].postfix = ""; + d[SI_AUDIOD_STATUS].fg = COLOR_WHITE; + d[SI_AUDIOD_STATUS].bg = COLOR_BLUE; + d[SI_AUDIOD_STATUS].align = CENTER; + d[SI_AUDIOD_STATUS].x = 50; + d[SI_AUDIOD_STATUS].y = 60; + d[SI_AUDIOD_STATUS].len = 50; + +} + +static void init_theme_colorful_blackness(struct gui_theme *t) +{ + struct stat_item_data *d = t->data; + t->name = "colorful blackness"; + t->author = "Andre Noll"; + /* minimal number of lines that is needed to display all + * information provided by this theme + */ + t->lines_min = 15; + t->cols_min = 80; + t->top_lines_min = 9; + t->top_lines_default = 11; /* default number of lines */ + + t->sb_bg = COLOR_GREEN; /* status bar background */ + t->sb_fg = COLOR_BLACK; /* status bar foreground */ + t->cmd_bg = COLOR_BLACK; + t->cmd_fg = COLOR_YELLOW; + t->output_bg = COLOR_BLACK; + t->output_fg = COLOR_CYAN; + t->msg_bg = COLOR_BLACK; + t->msg_fg = COLOR_WHITE; + t->err_msg_bg = COLOR_RED; + t->err_msg_fg = COLOR_WHITE; + t->welcome_bg = COLOR_BLUE; + t->welcome_fg = COLOR_WHITE; + t->sep_bg = COLOR_BLACK; /* color of the separator */ + t->sep_fg = COLOR_BLUE; + t->sep_str = "-"; + t->default_bg = COLOR_BLACK; + t->default_fg = COLOR_MAGENTA; + + + d[SI_PLAY_TIME].prefix = ""; + d[SI_PLAY_TIME].postfix = ""; + d[SI_PLAY_TIME].fg = COLOR_CYAN; + d[SI_PLAY_TIME].bg = COLOR_BLACK; + d[SI_PLAY_TIME].align = CENTER; + d[SI_PLAY_TIME].x = 0; + d[SI_PLAY_TIME].y = 7; + d[SI_PLAY_TIME].len = 35; + + d[SI_STATUS_BAR].prefix = ""; + d[SI_STATUS_BAR].postfix = ""; + d[SI_STATUS_BAR].fg = COLOR_CYAN; + d[SI_STATUS_BAR].bg = COLOR_BLACK; + d[SI_STATUS_BAR].align = LEFT; + d[SI_STATUS_BAR].x = 35; + d[SI_STATUS_BAR].y = 7; + d[SI_STATUS_BAR].len = 65; + + d[SI_STATUS].prefix = ""; + d[SI_STATUS].postfix = " "; + d[SI_STATUS].fg = COLOR_RED; + d[SI_STATUS].bg = COLOR_BLACK; + d[SI_STATUS].align = RIGHT; + d[SI_STATUS].x = 0; + d[SI_STATUS].y = 17; + d[SI_STATUS].len = 11; + + d[SI_STATUS_FLAGS].prefix = "("; + d[SI_STATUS_FLAGS].postfix = ")"; + d[SI_STATUS_FLAGS].fg = COLOR_RED; + d[SI_STATUS_FLAGS].bg = COLOR_BLACK; + d[SI_STATUS_FLAGS].align = LEFT; + d[SI_STATUS_FLAGS].x = 11; + d[SI_STATUS_FLAGS].y = 17; + d[SI_STATUS_FLAGS].len = 10; + + d[SI_DBTOOL].prefix = "dbtool: "; + d[SI_DBTOOL].postfix = ""; + d[SI_DBTOOL].fg = COLOR_RED; + d[SI_DBTOOL].bg = COLOR_BLACK; + d[SI_DBTOOL].align = CENTER; + d[SI_DBTOOL].x = 21; + d[SI_DBTOOL].y = 17; + d[SI_DBTOOL].len = 20; + + d[SI_FORMAT].prefix = "format: "; + d[SI_FORMAT].postfix = ""; + d[SI_FORMAT].fg = COLOR_RED; + d[SI_FORMAT].bg = COLOR_BLACK; + d[SI_FORMAT].align = CENTER; + d[SI_FORMAT].x = 42; + d[SI_FORMAT].y = 17; + d[SI_FORMAT].len = 14; + + + d[SI_NUM_PLAYED].prefix = "#"; + d[SI_NUM_PLAYED].postfix = ""; + d[SI_NUM_PLAYED].fg = COLOR_RED; + d[SI_NUM_PLAYED].bg = COLOR_BLACK; + d[SI_NUM_PLAYED].align = CENTER; + d[SI_NUM_PLAYED].x = 60; + d[SI_NUM_PLAYED].y = 17; + d[SI_NUM_PLAYED].len = 15; + + d[SI_UPTIME].prefix = "SUp: "; + d[SI_UPTIME].postfix = ""; + d[SI_UPTIME].fg = COLOR_RED; + d[SI_UPTIME].bg = COLOR_BLACK; + d[SI_UPTIME].align = CENTER; + d[SI_UPTIME].x = 75; + d[SI_UPTIME].y = 17; + d[SI_UPTIME].len = 25; + + d[SI_AUDIOD_STATUS].prefix = "audiod: "; + d[SI_AUDIOD_STATUS].postfix = ""; + d[SI_AUDIOD_STATUS].fg = COLOR_MAGENTA; + d[SI_AUDIOD_STATUS].bg = COLOR_BLACK; + d[SI_AUDIOD_STATUS].align = RIGHT; + d[SI_AUDIOD_STATUS].x = 0; + d[SI_AUDIOD_STATUS].y = 27; + d[SI_AUDIOD_STATUS].len = 13; + + + d[SI_DECODER_FLAGS].prefix = "["; + d[SI_DECODER_FLAGS].postfix = "]"; + d[SI_DECODER_FLAGS].fg = COLOR_MAGENTA; + d[SI_DECODER_FLAGS].bg = COLOR_BLACK; + d[SI_DECODER_FLAGS].align = LEFT; + d[SI_DECODER_FLAGS].x = 14; + d[SI_DECODER_FLAGS].y = 27; + d[SI_DECODER_FLAGS].len = 8; + + d[SI_MTIME].prefix = "Mtime: "; + d[SI_MTIME].postfix = ""; + d[SI_MTIME].fg = COLOR_MAGENTA; + d[SI_MTIME].bg = COLOR_BLACK; + d[SI_MTIME].align = CENTER; + d[SI_MTIME].x = 22; + d[SI_MTIME].y = 27; + d[SI_MTIME].len = 30; + + d[SI_FILE_SIZE].prefix = "Size: "; + d[SI_FILE_SIZE].postfix = " kb"; + d[SI_FILE_SIZE].fg = COLOR_MAGENTA; + d[SI_FILE_SIZE].bg = COLOR_BLACK; + d[SI_FILE_SIZE].align = CENTER; + d[SI_FILE_SIZE].x = 52; + d[SI_FILE_SIZE].y = 27; + d[SI_FILE_SIZE].len = 26; + + d[SI_AUDIOD_UPTIME].prefix = "AUp: "; + d[SI_AUDIOD_UPTIME].postfix = ""; + d[SI_AUDIOD_UPTIME].fg = COLOR_MAGENTA; + d[SI_AUDIOD_UPTIME].bg = COLOR_BLACK; + d[SI_AUDIOD_UPTIME].align = CENTER; + d[SI_AUDIOD_UPTIME].x = 78; + d[SI_AUDIOD_UPTIME].y = 27; + d[SI_AUDIOD_UPTIME].len = 22; + + + d[SI_AUDIO_INFO1].prefix = ""; + d[SI_AUDIO_INFO1].postfix = ""; + d[SI_AUDIO_INFO1].fg = COLOR_GREEN; + d[SI_AUDIO_INFO1].bg = COLOR_BLACK; + d[SI_AUDIO_INFO1].align = CENTER; + d[SI_AUDIO_INFO1].x = 0; + d[SI_AUDIO_INFO1].y = 43; + d[SI_AUDIO_INFO1].len = 100; + + d[SI_AUDIO_INFO2].prefix = ""; + d[SI_AUDIO_INFO2].postfix = ""; + d[SI_AUDIO_INFO2].fg = COLOR_GREEN; + d[SI_AUDIO_INFO2].bg = COLOR_BLACK; + d[SI_AUDIO_INFO2].align = CENTER; + d[SI_AUDIO_INFO2].x = 0; + d[SI_AUDIO_INFO2].y = 53; + d[SI_AUDIO_INFO2].len = 100; + + d[SI_AUDIO_INFO3].prefix = ""; + d[SI_AUDIO_INFO3].postfix = ""; + d[SI_AUDIO_INFO3].fg = COLOR_GREEN; + d[SI_AUDIO_INFO3].bg = COLOR_BLACK; + d[SI_AUDIO_INFO3].align = CENTER; + d[SI_AUDIO_INFO3].x = 0; + d[SI_AUDIO_INFO3].y = 63; + d[SI_AUDIO_INFO3].len = 100; + + d[SI_DBINFO1].prefix = ""; + d[SI_DBINFO1].postfix = ""; + d[SI_DBINFO1].fg = COLOR_YELLOW; + d[SI_DBINFO1].bg = COLOR_BLACK; + d[SI_DBINFO1].align = CENTER; + d[SI_DBINFO1].x = 0; + d[SI_DBINFO1].y = 77; + d[SI_DBINFO1].len = 100; + + d[SI_DBINFO2].prefix = ""; + d[SI_DBINFO2].postfix = ""; + d[SI_DBINFO2].fg = COLOR_YELLOW; + d[SI_DBINFO2].bg = COLOR_BLACK; + d[SI_DBINFO2].align = CENTER; + d[SI_DBINFO2].x = 0; + d[SI_DBINFO2].y = 87; + d[SI_DBINFO2].len = 100; + + d[SI_DBINFO3].prefix = ""; + d[SI_DBINFO3].postfix = ""; + d[SI_DBINFO3].fg = COLOR_YELLOW; + d[SI_DBINFO3].bg = COLOR_BLACK; + d[SI_DBINFO3].align = CENTER; + d[SI_DBINFO3].x = 0; + d[SI_DBINFO3].y = 97; + d[SI_DBINFO3].len = 100; +} + +void init_theme(int num, struct gui_theme *t) +{ + int i; + + for (i = 0; i < NUM_STAT_ITEMS; i++) + t->data[i].len = 0; + current_theme_num = num; + + return (num % NUM_THEMES)? + init_theme_simple(t) : init_theme_colorful_blackness(t); +} + +void prev_theme(struct gui_theme *t) +{ + return init_theme(++current_theme_num, t); +} + +void next_theme(struct gui_theme *t) +{ + return init_theme(--current_theme_num, t); +} + diff --git a/http.h b/http.h new file mode 100644 index 00000000..5708c7b0 --- /dev/null +++ b/http.h @@ -0,0 +1,3 @@ +/** \file http.h macros used by http_send and http_recv */ +#define HTTP_OK_MSG "HTTP/1.0 200 OK\r\n\r\n" +#define HTTP_GET_MSG "GET / HTTP/1.0" diff --git a/http_recv.c b/http_recv.c new file mode 100644 index 00000000..2e3a11b3 --- /dev/null +++ b/http_recv.c @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2005-2006 Andre Noll + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ + +/** \file http_recv.c paraslash's http receiver */ + +#include "para.h" + +#include "http.h" +#include "recv.h" +#include "http_recv.cmdline.h" +#include "error.h" +#include "net.h" +#include "string.h" + +/** the output buffer size of the http receiver */ +#define BUFSIZE (32 * 1024) + +/** + * the possible states of a http receiver node + * + * \sa receiver_node + */ +enum http_recv_status {HTTP_CONNECTED, HTTP_SENT_GET_REQUEST, HTTP_STREAMING}; + +/** + * data specific to the http receiver + * + * Each running instance of the http receiver reserves space for one such struct. + */ +struct private_http_recv_data { +/** + * + * + * the current status of the http receiver node + * + * It gets initialized to #HTTP_CONNECTED by the open function of the + * http receiver. + * + * \sa receiver::open, receiver_node. + */ + enum http_recv_status status; +/** + * + * + * the file descriptor used for receiving the http stream + * + * The pre_select function of the http receiver adds this file descriptor to + * the set of file decriptors which are checked for reading/writing (depending + * on the current status) by the select loop of the application (para_audiod or + * para_recv). + * + * The post_select function of the http receiver uses \a fd, if ready, to + * establish the http connection, and updates \a status according to the new + * state of the connection. As soon as \a status is #HTTP_STREAMING, \a fd is + * going to be only checked for reading. If data is available, it is read into + * the output buffer of the receiver node by post_select. + * + * \sa receiver::pre_select receiver::post_select receiver_node + */ + int fd; +}; + +static void http_shutdown(void) +{ + return; +} + +static char *make_request_msg(void) +{ + char *ret, *hn = para_hostname(); + ret = make_message("%s\nHost: %s\nUser-Agent: para_recv/%s\n\n\n", + HTTP_GET_MSG, hn, VERSION); + free(hn); + return ret; +} + +static int http_pre_select(struct receiver_node *rn, fd_set *rfds, fd_set *wfds, + __unused struct timeval *timeout) +{ + struct private_http_recv_data *phd = rn->private_data; + + if (phd->status == HTTP_CONNECTED) + FD_SET(phd->fd, wfds); + else + FD_SET(phd->fd, rfds); + return phd->fd; +} + +static int http_post_select(struct receiver_node *rn, int select_ret, + fd_set *rfds, fd_set *wfds) +{ + int ret; + struct private_http_recv_data *phd = rn->private_data; + + if (!select_ret) /* we're not interested in timeouts */ + return 1; + if (phd->status == HTTP_CONNECTED) { + char *rq; + if (!FD_ISSET(phd->fd, wfds)) + return 1; /* nothing to do */ + rq = make_request_msg(); + PARA_NOTICE_LOG("%s", "sending http request\n"); + ret = send_va_buffer(phd->fd, "%s", rq); + free(rq); + if (ret < 0) + return E_SEND_HTTP_REQUEST; + phd->status = HTTP_SENT_GET_REQUEST; + return 1; + } + if (!FD_ISSET(phd->fd, rfds)) + return 1; /* nothing to do */ + if (phd->status == HTTP_SENT_GET_REQUEST) { + ret = recv_pattern(phd->fd, HTTP_OK_MSG, MAXLINE); + if (ret < 0) + return -E_MISSING_OK; + PARA_NOTICE_LOG("%s", "received ok msg, streaming\n"); + phd->status = HTTP_STREAMING; + return 1; + } + /* already streaming */ + if (rn->loaded >= BUFSIZE) { + PARA_ERROR_LOG("%s", "buffer overrun\n"); + return -E_OVERRUN; + } + ret = recv_bin_buffer(phd->fd, rn->buf + rn->loaded, BUFSIZE - rn->loaded); + if (ret <= 0) { + PARA_NOTICE_LOG("recv returned %d/%d\n", ret, BUFSIZE - rn->loaded); + return ret < 0? -E_HTTP_RECV_BUF : 0; + } + rn->loaded += ret; + return 1; +} + +static void http_recv_close(struct receiver_node *rn) +{ + struct private_http_recv_data *phd = rn->private_data; + close(phd->fd); + free(rn->buf); + free(rn->private_data); +} + +static void *http_recv_parse_config(int argc, char **argv) +{ + struct gengetopt_args_info *tmp = para_calloc(sizeof(struct gengetopt_args_info)); + + if (!http_recv_cmdline_parser(argc, argv, tmp)) + return tmp; + free(tmp); + return NULL; +} + +static int http_recv_open(struct receiver_node *rn) +{ + struct private_http_recv_data *phd; + struct hostent *he; + struct gengetopt_args_info *conf = rn->conf; + struct sockaddr_in their_addr; + int ret; + + rn->buf = para_calloc(BUFSIZE); + rn->private_data = para_calloc(sizeof(struct private_http_recv_data)); + phd = rn->private_data; + optind = 0; + ret = -E_HOST_INFO; + if (!(he = get_host_info(conf->host_arg))) + goto err_out; + /* get new socket */ + ret = -E_SOCKET; + if ((phd->fd = get_socket()) < 0) + goto err_out; + /* init their_addr */ + init_sockaddr(&their_addr, conf->port_arg, he); + /* connect */ + PARA_NOTICE_LOG("connecting to %s:%d\n", conf->host_arg, + conf->port_arg); + ret = para_connect(phd->fd, &their_addr); + if (ret < 0) + goto err_out; + phd->status = HTTP_CONNECTED; + return 1; +err_out: + free(rn->private_data); + free(rn->buf); + return ret; +} + +/** + * the init function of the http receiver + * + * \param r pointer to the receiver struct to initialize + * + * Just initialize all function pointers of \a r. + */ +void http_recv_init(struct receiver *r) +{ + r->open = http_recv_open; + r->close = http_recv_close; + r->pre_select = http_pre_select; + r->post_select = http_post_select; + r->shutdown = http_shutdown; + r->parse_config = http_recv_parse_config; +} diff --git a/http_recv.ggo b/http_recv.ggo new file mode 100644 index 00000000..5c4ef4ec --- /dev/null +++ b/http_recv.ggo @@ -0,0 +1,2 @@ +option "host" i "ip or host" string default="localhost" no +option "port" p "tcp port to connect to" int default="8000" no diff --git a/http_send.c b/http_send.c new file mode 100644 index 00000000..c574ba3c --- /dev/null +++ b/http_send.c @@ -0,0 +1,586 @@ +/* + * Copyright (C) 2005-2006 Andre Noll + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ + +/** \file http_send.c paraslash's http sender */ + + +#include "server.cmdline.h" +#include "server.h" +#include "http.h" +#include "afs.h" +#include "send.h" +#include "list.h" +#include "close_on_fork.h" +#include "error.h" +#include "net.h" +#include "string.h" + +/** \cond convert sock_addr_in to ascii */ +#define CLIENT_ADDR(hc) inet_ntoa((hc)->addr.sin_addr) +/* get the port number of a struct http_client */ +#define CLIENT_PORT(hc) (hc)->addr.sin_port +#define HTTP_ERR_MSG "HTTP/1.0 400 Bad Request\n" +/** \endcond */ + +extern struct gengetopt_args_info conf; + +/** the possible states of a client from the server's POV */ +enum http_status { + HTTP_CONNECTED, + HTTP_GOT_GET_REQUEST, + HTTP_SENT_OK_MSG, + HTTP_READY_TO_STREAM, + HTTP_STREAMING, + HTTP_INVALID_GET_REQUEST +}; + +/** clients will be kicked if there are more than that many bytes pending */ +#define MAX_BACKLOG 40000 +/** the list of connected clients **/ +static struct list_head clients; +/** the whitelist/blacklist */ +static struct list_head access_perm_list; + +/** describes one client that connected the tcp port of the http sender */ +struct http_client { +/** the file descriptor of the client */ + int fd; +/** address information about the client */ + struct sockaddr_in addr; +/** the client's current status */ + enum http_status status; +/** non-zero if we included \a fd in the read set */ + int check_r; +/** non-zero if we included \a fd in the write set */ + int check_w; +/** the position of this client in the client list */ + struct list_head node; +/** the list of pending packets for this client */ + struct list_head packet_queue; +/** the number of pending bytes for this client */ + unsigned long pq_bytes; +}; + +/** + * describes one queued data packet for a client + * + * The send function of the http sender checks each client fd for writing. If a + * client fd is not ready, it tries to queue that packet for this client until + * the number of queued bytes exceeds \p MAX_BACKLOG. + */ +struct queued_packet { +/** the length of the packet in bytes */ + unsigned int len; +/** pointer to the packet data */ + char *packet; +/** position of the packet in the packet list */ + struct list_head node; +}; + +/** + * describes one entry in the blacklist/whitelist of the http sender + */ +struct access_info { + /** the address to be black/whitelisted */ + struct in_addr addr; + /** the netmask for this entry */ + int netmask; + /** the position of this entry in the access_perm_list */ + struct list_head node; +}; + +static int server_fd = -1, numclients; +static struct sender *self; + + +static void http_shutdown_client(struct http_client *hc, const char *msg) +{ + struct queued_packet *qp, *tmp; + PARA_INFO_LOG("shutting down %s on fd %d (%s)\n", CLIENT_ADDR(hc), + hc->fd, msg); + numclients--; + close(hc->fd); + + list_for_each_entry_safe(qp, tmp, &hc->packet_queue, node) { + free(qp->packet); + list_del(&qp->node); + free(qp); + } + list_del(&hc->node); + free(hc); + return; +} + +static void http_shutdown_clients_real(void) +{ + struct http_client *hc, *tmp; + list_for_each_entry_safe(hc, tmp, &clients, node) + http_shutdown_client(hc, "afs request"); +} +static void http_shutdown_clients(void) +{ + struct http_client *hc, *tmp; + list_for_each_entry_safe(hc, tmp, &clients, node) + if (hc->status == HTTP_STREAMING) + http_shutdown_client(hc, "afs request"); +} + +static int http_send_msg(struct http_client *hc, const char *msg) +{ + int ret = send_buffer(hc->fd, msg); + + if (ret < 0) + http_shutdown_client(hc, "send msg failed"); + return ret; +} + +static void http_send_ok_msg(struct http_client *hc) +{ + PARA_INFO_LOG("sending http ok message to fd %d\n", hc->fd); + http_send_msg(hc, HTTP_OK_MSG); +} + +static int http_send_err_msg(struct http_client *hc) +{ + PARA_NOTICE_LOG("sending bad request message to fd %d\n", hc->fd); + return http_send_msg(hc, HTTP_ERR_MSG); +} + +static int queue_packet(struct http_client *hc, const char *buf, size_t len) +{ + struct queued_packet *qp; + if (hc->pq_bytes + len > MAX_BACKLOG) { + http_shutdown_client(hc, "packet queue overrun"); + return -E_QUEUE; + } + qp = para_malloc(sizeof(struct queued_packet)); + hc->pq_bytes += len; + qp->packet = para_malloc(len); + memcpy(qp->packet, buf, len); + qp->len = len; + list_add_tail(&qp->node, &hc->packet_queue); + PARA_INFO_LOG("%lu bytes queued for fd %d\n", hc->pq_bytes, hc->fd); + return 1; +} + +static int write_ok(int fd) +{ + struct timeval tv = {0, 0}; + fd_set wfds; + int ret; +again: + FD_ZERO(&wfds); + FD_SET(fd, &wfds); + ret = select(fd + 1, NULL, &wfds, NULL, &tv); + if (ret < 0 && errno == EINTR) + goto again; + if (ret < 0) + ret = -E_WRITE_OK; + return ret; +} + + +static int send_queued_packets(struct http_client *hc) +{ + int ret; + struct queued_packet *qp, *tmp; + + if (list_empty(&hc->packet_queue)) + return 1; + list_for_each_entry_safe(qp, tmp, &hc->packet_queue, node) { + ret = write_ok(hc->fd); + if (ret <= 0) + return ret; + ret = write(hc->fd, qp->packet, qp->len); + if (ret < 0) + return ret; + if (ret != qp->len) { + qp->len -= ret; + memmove(qp->packet, qp->packet + ret, qp->len); + return 0; + } + hc->pq_bytes -= qp->len; + free(qp->packet); + list_del(&qp->node); + free(qp); + } + return 1; +} + +static void http_send(__unused struct audio_format *af, + long unsigned current_chunk, + __unused long unsigned chunks_sent, const char *buf, size_t len) +{ + struct http_client *hc, *tmp; + int ret; + + list_for_each_entry_safe(hc, tmp, &clients, node) { + if (hc->status != HTTP_STREAMING && + hc->status != HTTP_READY_TO_STREAM) + continue; + if (hc->status == HTTP_READY_TO_STREAM) { + if (af->get_header_info && current_chunk) { + /* need to send header */ + int hlen; + char *buf = af->get_header_info(&hlen); + if (!buf || hlen <= 0) + continue; /* header not yet available */ + PARA_INFO_LOG("queueing header: %d\n", hlen); + if (queue_packet(hc, buf, hlen) < 0) + continue; + } else + PARA_INFO_LOG("%s", "no need to queue header\n"); + hc->status = HTTP_STREAMING; + } + ret = send_queued_packets(hc); + if (ret < 0) { + http_shutdown_client(hc, "send error"); + continue; + } + if (!len) + continue; + if (!ret || write_ok(hc->fd) <= 0) { + PARA_INFO_LOG("fd %d not ready (%lu bytes queued)," + " trying to queue packet\n", hc->fd, + hc->pq_bytes); + queue_packet(hc, buf, len); + continue; + } +// PARA_DEBUG_LOG("sending %d -> %s\n", len, CLIENT_ADDR(hc)); + ret = write(hc->fd, buf, len); + if (ret < 0) { + http_shutdown_client(hc, "send error"); + continue; + } + if (ret != len) + queue_packet(hc, buf + ret, len - ret); + } +} + +static int host_in_access_perm_list(struct http_client *hc) +{ + struct access_info *ai, *tmp; + list_for_each_entry_safe(ai, tmp, &access_perm_list, node) { + unsigned mask = ((~0) >> ai->netmask); + if ((hc->addr.sin_addr.s_addr & mask) == (ai->addr.s_addr & mask)) + return 1; + } + return 0; +} + +static void http_post_select(__unused struct audio_format *af, fd_set *rfds, + fd_set *wfds) +{ + int i = -1, match; + struct http_client *hc, *tmp; + char *err_msg; + + list_for_each_entry_safe(hc, tmp, &clients, node) { + i++; +// PARA_DEBUG_LOG("handling client %d: %s\n", i, CLIENT_ADDR(hc)); + switch (hc->status) { + case HTTP_STREAMING: /* nothing to do */ + case HTTP_READY_TO_STREAM: + break; + case HTTP_CONNECTED: /* need to recv get request */ + if (hc->check_r && FD_ISSET(hc->fd, rfds)) { + if (recv_pattern(hc->fd, HTTP_GET_MSG, MAXLINE) + < 0) { + hc->status = HTTP_INVALID_GET_REQUEST; + } else { + hc->status = HTTP_GOT_GET_REQUEST; + PARA_INFO_LOG("%s", + "received get request\n"); + } + } + break; + case HTTP_GOT_GET_REQUEST: /* need to send ok msg */ + if (hc->check_w && FD_ISSET(hc->fd, wfds)) { + hc->status = HTTP_SENT_OK_MSG; + http_send_ok_msg(hc); + } + break; + case HTTP_INVALID_GET_REQUEST: /* need to send err msg */ + if (hc->check_w && FD_ISSET(hc->fd, wfds)) { + if (http_send_err_msg(hc) >= 0) + http_shutdown_client(hc, + "invalid get request"); + } + break; + case HTTP_SENT_OK_MSG: /* need to send header? */ + if (hc->check_w && FD_ISSET(hc->fd, wfds)) + hc->status = HTTP_READY_TO_STREAM; + break; + } + } + if (!FD_ISSET(server_fd, rfds)) + return; + hc = para_calloc(sizeof(struct http_client)); + err_msg = "accept error"; + hc->fd = para_accept(server_fd, &hc->addr, sizeof(struct sockaddr_in)); + if (hc->fd <= 0) + goto err_out; + PARA_NOTICE_LOG("connection from %s (fd %d)\n", CLIENT_ADDR(hc), hc->fd); + if (conf.http_max_clients_arg > 0 && numclients >= + conf.http_max_clients_arg) { + err_msg = "server full"; + goto err_out; + } + match = host_in_access_perm_list(hc); + PARA_DEBUG_LOG("host_in_access_perm_list: %d\n", match); + if ((match && !conf.http_default_deny_given) || + (!match && conf.http_default_deny_given)) { + err_msg = "permission denied"; + goto err_out; + } + hc->status = HTTP_CONNECTED; + INIT_LIST_HEAD(&hc->packet_queue); + PARA_INFO_LOG("accepted client #%d: %s (fd %d)\n", numclients, + CLIENT_ADDR(hc), hc->fd); + numclients++; + list_add(&hc->node, &clients); + return; +err_out: + PARA_WARNING_LOG("ignoring connect request from %s (%s)\n", + CLIENT_ADDR(hc), err_msg); + if (hc->fd > 0) + close(hc->fd); + free(hc); +} + +static void http_pre_select(struct audio_format *af, int *max_fileno, fd_set *rfds, + fd_set *wfds) +{ + struct http_client *hc, *tmp; + + if (server_fd < 0) + return; + FD_SET(server_fd, rfds); + *max_fileno = MAX(*max_fileno, server_fd); + list_for_each_entry_safe(hc, tmp, &clients, node) { + //PARA_DEBUG_LOG("hc %p on fd %d: status %d\n", hc, hc->fd, hc->status); + hc->check_r = 0; + hc->check_w = 0; + switch (hc->status) { + case HTTP_STREAMING: + case HTTP_READY_TO_STREAM: + break; + case HTTP_CONNECTED: /* need to recv get request */ + FD_SET(hc->fd, rfds); + *max_fileno = MAX(*max_fileno, hc->fd); + hc->check_r = 1; + break; + case HTTP_GOT_GET_REQUEST: /* need to send ok msg */ + case HTTP_INVALID_GET_REQUEST: /* need to send err msg */ + FD_SET(hc->fd, wfds); + *max_fileno = MAX(*max_fileno, hc->fd); + hc->check_w = 1; + break; + case HTTP_SENT_OK_MSG: + if (!af || !afs_playing()) + break; /* wait until server starts playing */ + FD_SET(hc->fd, wfds); + *max_fileno = MAX(*max_fileno, hc->fd); + hc->check_w = 1; + break; + } + } +} + +static int open_tcp_port(int port) +{ + server_fd = init_tcp_socket(port); + if (server_fd < 0) { + http_shutdown_clients_real(); + self->status = SENDER_OFF; + return server_fd; + } + self->status = SENDER_ON; + add_close_on_fork_list(server_fd); + return 1; +} + +static int http_com_on(__unused struct sender_command_data *scd) +{ + if (self->status == SENDER_ON) + return 1; + return open_tcp_port(conf.http_port_arg); +} + +static int http_com_off(__unused struct sender_command_data *scd) +{ + self->status = SENDER_OFF; + if (server_fd > 0) { + close(server_fd); + del_close_on_fork_list(server_fd); + server_fd = -1; + } + http_shutdown_clients_real(); + return 1; +} + +static void del_perm_list_entry(struct sender_command_data *scd) +{ + struct access_info *ai, *tmp; + + list_for_each_entry_safe(ai, tmp, &access_perm_list, node) { + char *nad = para_strdup(inet_ntoa(ai->addr)); + if (!strcmp(nad, inet_ntoa(scd->addr)) && + ai->netmask == scd->netmask) { + PARA_NOTICE_LOG("removing %s/%i from access list\n", + nad, ai->netmask); + list_del(&ai->node); + free(ai); + } + free(nad); + } +} + +static void add_perm_list_entry(struct sender_command_data *scd) +{ + struct access_info *ai = para_malloc(sizeof(struct access_info)); + ai->addr = scd->addr; + ai->netmask = scd->netmask; + PARA_INFO_LOG("adding %s/%i to access list\n", inet_ntoa(ai->addr), + ai->netmask); + list_add(&ai->node, &access_perm_list); +} + +static int http_com_deny(struct sender_command_data *scd) +{ + if (conf.http_default_deny_given) + del_perm_list_entry(scd); + else + add_perm_list_entry(scd); + return 1; +} + +static int http_com_allow(struct sender_command_data *scd) +{ + if (conf.http_default_deny_given) + add_perm_list_entry(scd); + else + del_perm_list_entry(scd); + return 1; +} + +static char *http_info(void) +{ + char *clnts = NULL, *ap = NULL, *ret; + struct access_info *ai, *tmp_ai; + struct http_client *hc, *tmp_hc; + + list_for_each_entry_safe(ai, tmp_ai, &access_perm_list, node) { + char *tmp = make_message("%s%s/%d ", ap? ap : "", + inet_ntoa(ai->addr), ai->netmask); + free(ap); + ap = tmp; + } + list_for_each_entry_safe(hc, tmp_hc, &clients, node) { + char *tmp = make_message("%s%s:%d ", clnts? clnts : "", + CLIENT_ADDR(hc), CLIENT_PORT(hc)); + free(clnts); + clnts = tmp; + } + ret = make_message( + "http status: %s\n" + "http tcp port: %d\n" + "http clients: %d\n" + "http maximal number of clients: %d%s\n" + "http connected clients: %s\n" + "http access %s list: %s\n", + (self->status == SENDER_ON)? "on" : "off", + conf.http_port_arg, + numclients, + conf.http_max_clients_arg, + conf.http_max_clients_arg > 0? "" : " (unlimited)", + clnts? clnts : "(none)", + conf.http_default_deny_given? "allow" : "deny", + ap? ap : "(none)" + ); + free(ap); + free(clnts); + return ret; +} + +static void init_access_control_list(void) +{ + int i; + struct sender_command_data scd; + + INIT_LIST_HEAD(&access_perm_list); + for (i = 0; i < conf.http_access_given; i++) { + char *arg = para_strdup(conf.http_access_arg[i]); + char *p = strchr(arg, '/'); + if (!p) + goto err; + *p = '\0'; + if (!inet_aton(arg, &scd.addr)) + goto err; + scd.netmask = atoi(++p); + if (scd.netmask < 0 || scd.netmask > 32) + goto err; + add_perm_list_entry(&scd); + goto success; +err: + PARA_CRIT_LOG("syntax error for http_access option " + "#%d, ignoring\n", i); +success: + free(arg); + continue; + } +} + +static char *http_help(void) +{ + return make_message( + "usage: {on|off}\n" + "usage: {allow|deny} IP mask\n" + "example: allow 127.0.0.1 32\n" + ); +} + +/** + * the init function of the http sender + * + * \param s pointer to the http sender struct + * + * It initializes all function pointers of \a s, init the client list and the + * acess control list as well. If autostart is wanted, open the tcp port. + */ +void http_send_init(struct sender *s) +{ + INIT_LIST_HEAD(&clients); + s->info = http_info; + s->send = http_send; + s->pre_select = http_pre_select; + s->post_select = http_post_select; + s->shutdown_clients = http_shutdown_clients; + s->help = http_help; + s->client_cmds[SENDER_ON] = http_com_on; + s->client_cmds[SENDER_OFF] = http_com_off; + s->client_cmds[SENDER_DENY] = http_com_deny; + s->client_cmds[SENDER_ALLOW] = http_com_allow; + s->client_cmds[SENDER_ADD] = NULL; + s->client_cmds[SENDER_DELETE] = NULL; + self = s; + init_access_control_list(); + if (!conf.http_no_autostart_given) + open_tcp_port(conf.http_port_arg); + PARA_DEBUG_LOG("%s", "http sender init complete\n"); +} diff --git a/index.html b/index.html new file mode 100644 index 00000000..acb329ca --- /dev/null +++ b/index.html @@ -0,0 +1,263 @@ + + + + + Paraslash + + + + + + + + + + + + + +
+ paraslash
+
+

Paraslash: Play, archive, rate and stream + large audio sets happily

+ + A set of tools for doing just what its name + suggests. +
+
Feature list +
Screenshots +
Download +
Live Demo +
Changes
Documentation +
License
Author +
+
+

Events

+
    +
  • 2005-12-27: paraslash-0.2.7 "transparent invariance"
  • +
  • 2005-10-29: paraslash-0.2.6 "recursive compensation"
  • +
  • 2005-10-27: manual pages online
  • +
  • 2005-10-13: paraslash-0.2.5 "aggressive resolution"
  • +
  • 2005-09-21: paraslash-0.2.4 "toxic anticipation"
  • +
  • 2005-09-01: paraslash-0.2.3 "hydrophilic movement"
  • +
  • 2005-08-19: paraslash-0.2.2 "tangential excitation"
  • +
  • 2005-08-15: paraslash-0.2.1 "surreal experience"
  • +
  • 2005-08-06: overview.pdf
  • +
  • 2005-08-06: paraslash-0.2.0 "distributed diffusion"
  • +
  • 2005-08-01: paraslash live stream
  • +
  • 2005-04-18: paraslash-0.1.7 "melting penetration"
  • +
  • 2005-03-05: paraslash-0.1.6 "asymptotic balance"
  • +
  • 2004-12-31: paraslash-0.1.5 "opaque eternity"
  • +
  • 2004-12-19: paraslash-0.1.4 "tunneling transition"
  • +
  • 2004-12-10: paraslash-0.1.3 "vanishing inertia"
  • +
  • 2004-11-28: paraslash-0.1.2 "spherical fluctuation"
  • +
  • 2004-11-05: paraslash-0.1.1 "floating atmosphere"
  • +
  • 2004-10-22: paraslash-0.1.0 "rotating cortex"
  • +
+
+

Feature list

+
    +
  • network audio streaming software
  • +
  • client/server tcp-networking
  • +
  • command line interface
  • +
  • openssl user authentication
  • +
  • several grafical user interfaces
  • +
  • mysql-based audio file selector
  • +
+ See FEATURES for a more detailed list. +
+ +

Screenshots

+ Everybody loves screenshots, so + here + we go. +
+ +

Download

+ +

Only source is available, + including the + nightly cvs snapshot. All regular releases are + cryptographically signed. + Anonymous (read-only) cvs access is also + available. Checkout a copy with

+ +

cvs -d + :pserver:anonymous@cvs.systemlinux.org:/var/lib/cvs + login

+

(empty passwd)

+

cvs -d :pserver:anonymous@cvs.systemlinux.org:/var/lib/cvs co paraslash

+ + +

Finally, you can RTFS online. +


+ +

Live Demo

+ +

There is a public paraslash stream at www.paraslash.org, + streaming + + the music of Henri Petterson. + + + You can listen to the stream with any mp3 player that supports + http streaming. Both

+ +

mpg123 http://www.paraslash.org:8009/

+ + and + +

xmms http://www.paraslash.org:8009/

+ +

are known to work.

+ +

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 + + this shell script + + 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.

+ +
    +
  • + Check that both aplay and mpg123 are installed on your system +
  • + +
  • + Download + a recent paraslash package. You + you need paraslash-0.2.0 or later + for the demo, paraslash-0.1.x will not work. +
  • + +
  • + + Install the neccessary paraslash binaries + (you can safely ignore any warnings about + missing software): + +
      +
    • tar xjf paraslash-cvs.tar.bz2
    • +
    • cd paraslash-cvs
    • +
    • bin="para_client para_audioc para_audiod para_gui" # all we need +
    • (./configure --prefix="$HOME" && make $bin) > /dev/null
    • +
    • mkdir -p $HOME/bin; cp $bin $HOME/bin +
    • export PATH=$HOME/bin:$PATH + +
    + There should be no errors. +
  • +
  • + Get the key for the anonymous account on + www.paraslash.org: + +
      +
    • dir="$HOME/.paraslash"; server=www.paraslash.org
    • +
    • mkdir -p $dir
    • +
    • wget --directory-prefix=$dir http://$server/key.anonymous
    • +
    +
  • +
  • + Tell para_client that we want to connect to + www.paraslash.org as user anonymous: + +
      +
    • conf="$dir/client.conf"; socket="$dir/socket"
    • +
    • echo user \"anonymous\" > $conf
    • +
    • echo hostname \"$server\" >> $conf
    • +
    • echo key_file \"$dir/key.anonymous\" >> $conf
    • +
    • echo socket \"$socket\" >> $dir/audioc.conf
    • +
    +
  • +
  • + Start para_audiod +
    • + + para_audiod -d --stream_read_cmd + "mp3:mpg123 -s http://$server:8009/" + --stream_write_cmd "mp3:aplay -fcd" -L $dir/audiod.log + --socket=$socket + +
    +
  • + Start para_gui +
    • + para_gui +
    +
  • +

+ + +

Changes

+ Read the complete + ChangeLog + or the file + NEWS + containing a brief summary of the changes for each version. +
+ + +

Documentation

+ Have a look at this + overview, + a pdf file containing a sketch which illustrates how the pieces of paraslash work + together. Read + README + for general information, + INSTALL + for installation notes, and + README.mysql + for instructions on how to use the mysql database tool + shipped with paraslash. There is also an online version + of paraslash's + manual pages. +
+ +

License

+ Distribution of Paraslash is covered by the GNU GPL. See file + COPYING. +
+ +

Author

+ André Noll, + maan@systemlinux.org +

+ 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 + CREDITS + for an incomplete list of people. +


+ + + Last modified: + +

+ Valid HTML 4.01! +

+ +
+ + diff --git a/install-sh b/install-sh new file mode 100755 index 00000000..6ce63b9f --- /dev/null +++ b/install-sh @@ -0,0 +1,294 @@ +#!/bin/sh +# +# install - install a program, script, or datafile +# +# This originates from X11R5 (mit/util/scripts/install.sh), which was +# later released in X11R6 (xc/config/util/install.sh) with the +# following copyright and license. +# +# Copyright (C) 1994 X Consortium +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC- +# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the name of the X Consortium shall not +# be used in advertising or otherwise to promote the sale, use or other deal- +# ings in this Software without prior written authorization from the X Consor- +# tium. +# +# +# FSF changes to this file are in the public domain. +# +# Calling this script install-sh is preferred over install.sh, to prevent +# `make' implicit rules from creating a file called install from it +# when there is no Makefile. +# +# This script is compatible with the BSD install script, but was written +# from scratch. It can only install one file at a time, a restriction +# shared with many OS's install programs. + + +# set DOITPROG to echo to test this script + +# Don't use :- since 4.3BSD and earlier shells don't like it. +doit="${DOITPROG-}" + + +# put in absolute paths if you don't have them in your path; or use env. vars. + +mvprog="${MVPROG-mv}" +cpprog="${CPPROG-cp}" +chmodprog="${CHMODPROG-chmod}" +chownprog="${CHOWNPROG-chown}" +chgrpprog="${CHGRPPROG-chgrp}" +stripprog="${STRIPPROG-strip}" +rmprog="${RMPROG-rm}" +mkdirprog="${MKDIRPROG-mkdir}" + +transformbasename="" +transform_arg="" +instcmd="$mvprog" +chmodcmd="$chmodprog 0755" +chowncmd="" +chgrpcmd="" +stripcmd="" +rmcmd="$rmprog -f" +mvcmd="$mvprog" +src="" +dst="" +dir_arg="" + +while [ x"$1" != x ]; do + case $1 in + -c) instcmd=$cpprog + shift + continue;; + + -d) dir_arg=true + shift + continue;; + + -m) chmodcmd="$chmodprog $2" + shift + shift + continue;; + + -o) chowncmd="$chownprog $2" + shift + shift + continue;; + + -g) chgrpcmd="$chgrpprog $2" + shift + shift + continue;; + + -s) stripcmd=$stripprog + shift + continue;; + + -t=*) transformarg=`echo $1 | sed 's/-t=//'` + shift + continue;; + + -b=*) transformbasename=`echo $1 | sed 's/-b=//'` + shift + continue;; + + *) if [ x"$src" = x ] + then + src=$1 + else + # this colon is to work around a 386BSD /bin/sh bug + : + dst=$1 + fi + shift + continue;; + esac +done + +if [ x"$src" = x ] +then + echo "$0: no input file specified" >&2 + exit 1 +else + : +fi + +if [ x"$dir_arg" != x ]; then + dst=$src + src="" + + if [ -d "$dst" ]; then + instcmd=: + chmodcmd="" + else + instcmd=$mkdirprog + fi +else + +# Waiting for this to be detected by the "$instcmd $src $dsttmp" command +# might cause directories to be created, which would be especially bad +# if $src (and thus $dsttmp) contains '*'. + + if [ -f "$src" ] || [ -d "$src" ] + then + : + else + echo "$0: $src does not exist" >&2 + exit 1 + fi + + if [ x"$dst" = x ] + then + echo "$0: no destination specified" >&2 + exit 1 + else + : + fi + +# If destination is a directory, append the input filename; if your system +# does not like double slashes in filenames, you may need to add some logic + + if [ -d "$dst" ] + then + dst=$dst/`basename "$src"` + else + : + fi +fi + +## this sed command emulates the dirname command +dstdir=`echo "$dst" | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` + +# Make sure that the destination directory exists. +# this part is taken from Noah Friedman's mkinstalldirs script + +# Skip lots of stat calls in the usual case. +if [ ! -d "$dstdir" ]; then +defaultIFS=' + ' +IFS="${IFS-$defaultIFS}" + +oIFS=$IFS +# Some sh's can't handle IFS=/ for some reason. +IFS='%' +set - `echo "$dstdir" | sed -e 's@/@%@g' -e 's@^%@/@'` +IFS=$oIFS + +pathcomp='' + +while [ $# -ne 0 ] ; do + pathcomp=$pathcomp$1 + shift + + if [ ! -d "$pathcomp" ] ; + then + $mkdirprog "$pathcomp" + else + : + fi + + pathcomp=$pathcomp/ +done +fi + +if [ x"$dir_arg" != x ] +then + $doit $instcmd "$dst" && + + if [ x"$chowncmd" != x ]; then $doit $chowncmd "$dst"; else : ; fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd "$dst"; else : ; fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd "$dst"; else : ; fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd "$dst"; else : ; fi +else + +# If we're going to rename the final executable, determine the name now. + + if [ x"$transformarg" = x ] + then + dstfile=`basename "$dst"` + else + dstfile=`basename "$dst" $transformbasename | + sed $transformarg`$transformbasename + fi + +# don't allow the sed command to completely eliminate the filename + + if [ x"$dstfile" = x ] + then + dstfile=`basename "$dst"` + else + : + fi + +# Make a couple of temp file names in the proper directory. + + dsttmp=$dstdir/_inst.$$_ + rmtmp=$dstdir/_rm.$$_ + +# Trap to clean up temp files at exit. + + trap 'status=$?; rm -f "$dsttmp" "$rmtmp" && exit $status' 0 + trap '(exit $?); exit' 1 2 13 15 + +# Move or copy the file name to the temp name + + $doit $instcmd "$src" "$dsttmp" && + +# and set any options; do chmod last to preserve setuid bits + +# If any of these fail, we abort the whole thing. If we want to +# ignore errors from any of these, just make sure not to ignore +# errors from the above "$doit $instcmd $src $dsttmp" command. + + if [ x"$chowncmd" != x ]; then $doit $chowncmd "$dsttmp"; else :;fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd "$dsttmp"; else :;fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd "$dsttmp"; else :;fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd "$dsttmp"; else :;fi && + +# Now remove or move aside any old file at destination location. We try this +# two ways since rm can't unlink itself on some systems and the destination +# file might be busy for other reasons. In this case, the final cleanup +# might fail but the new file should still install successfully. + +{ + if [ -f "$dstdir/$dstfile" ] + then + $doit $rmcmd -f "$dstdir/$dstfile" 2>/dev/null || + $doit $mvcmd -f "$dstdir/$dstfile" "$rmtmp" 2>/dev/null || + { + echo "$0: cannot unlink or rename $dstdir/$dstfile" >&2 + (exit 1); exit + } + else + : + fi +} && + +# Now rename the file to the real destination. + + $doit $mvcmd "$dsttmp" "$dstdir/$dstfile" + +fi && + +# The final little trick to "correctly" pass the exit status to the exit trap. + +{ + (exit 0); exit +} diff --git a/key.anonymous b/key.anonymous new file mode 100644 index 00000000..ebe05c1d --- /dev/null +++ b/key.anonymous @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBPQIBAAJBANNQC7sHwu9B33OIvC+hGIRXXQX3YAC1fWVmhUo/UMIzLTnCwqGN +ZPtrWPRfCoLA+ltf+cElXlq+DPAbHLdINpcCAwEAAQJBAIsDe9QctQcFROUSrQqA +ZkqZ1p6YMNYGj2nn2gQQRyaZphkw92cJOfO2Fco35rcrSMFPVVEUmds6oZX6uDNP +SyECIQDv/F5mDHnhopFynlO2yccMIQ5e7B25Qd728TopHubnmQIhAOFp3AP9uzBt +Q9H6zoHRHoHugU4RBnzH5sO39NqSyi2vAiEA6fc7vPfK15ybeog7C0iawwtzLD28 +GMHMcynozvAuo3kCIQCT5dkt5TUEuSxegaktFABoUA0XI6SWCrMmh3RoVRfCkQIh +AK1nC9pyoManFdtDXSL6fdpaLz7gIlJwkz5hgPW43bwe +-----END RSA PRIVATE KEY----- diff --git a/krell.c b/krell.c new file mode 100644 index 00000000..3f43151c --- /dev/null +++ b/krell.c @@ -0,0 +1,416 @@ +/* + * Copyright (C) 2004-2005 Andre Noll + * + * 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 +#include +#include +#include +#include +#include +#include + +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 \n" + "Copyright (C) 2004-2005\n" + "Distributed under the GNU General Public License.\n"; + +#define MAXLINE 255 + +static gboolean launch_cmd(char *cmd) +{ + + gchar **argv; + GError *err = NULL; + gboolean res; + + PRINTF("%s: \n", __func__); + if (!cmd || *cmd == '\0') + return -1; + + g_shell_parse_argv(cmd, NULL, &argv, NULL); + res = g_spawn_async( + NULL, + argv, + NULL, + G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL, + NULL, + NULL, + NULL, + &err + ); + if (!res && err) { + gkrellm_message_dialog(NULL, err->message); + g_error_free(err); + } + g_strfreev(argv); + return res; +} + +static gboolean cb_in_button(GkrellmDecalbutton *b, + GdkEventButton *ev, ControlButton *cbut) +{ + gint x, y, area, width = gkrellm_chart_width(); + x = (int) (3 * ev->x / width); + y = (int) (3 - 3 * ev->y / width); + area = 3 * y + x + 1; + char buf[MAXLINE]; + + PRINTF("%s: button %i pressed on area %i\n", __func__, + ev->button, area); + if (ev->button == 1) { + switch (area) { + case 1: + launch_cmd("para_client stop"); + return 0; + case 2: + launch_cmd("xterm -e para_gui -a"); + return 0; + case 3: + launch_cmd("para_client pause"); + return 0; + case 4: + launch_cmd("para_client ps"); + return 0; + case 5: + launch_cmd("para_client play"); + return 0; + case 6: + launch_cmd("xterm -e para_dbadm"); + return 0; + case 7: + launch_cmd("para_slider"); + return 0; + case 8: + launch_cmd("para_sdl_gui -f"); + return 0; + case 9: + launch_cmd("para_client next"); + return 0; + } + return 0; + } + sprintf(buf, "%s %i", ev->button == 2? "aumix -v" : "para_client jmp", + area * 10); + return launch_cmd(buf); +} + +static void make_button(ControlButton *cbut, gint fn_id) +{ + PRINTF("%s: gkrellm_make_scaled_button\n", __func__); + cbut->button = gkrellm_make_scaled_button( + panel, + cbut->image, + NULL, + GINT_TO_POINTER(fn_id), + FALSE, + FALSE, + 2, + 0, + 1, + cbut->x, + cbut->y, + cbut->w, + cbut->h + ); + PRINTF("%s: making botton\n", __func__); + gkrellm_set_in_button_callback(cbut->button, cb_in_button, cbut); +} + + +static void load_img(void) +{ + gint width = gkrellm_chart_width(); + gint out = 0, ret; + FILE *pipe = NULL; + char buf[MAXLINE]; + size_t num_read = 0; + gchar *filename = gkrellm_make_data_file_name("para", "pic.jpg"); + + PRINTF("%s: Opening %s\n", __func__, filename); + if ((out = creat(filename, S_IRUSR | S_IWUSR)) < 0) { + perror("open"); + goto out; + } + pipe = popen("para_client pic", "r"); + if (!pipe) + goto out; + while ((ret = read(fileno(pipe), buf, sizeof(buf) - 1)) > 0) { + if (write(out, buf, ret) < 0) { + perror("Write"); + goto out; + } + num_read += ret; + } + if (ret < 0) { + PRINTF("%s: Read Error\n", __func__); + goto out; + } + PRINTF("%s: new pic created (%i bytes)\n", __func__, num_read); + if (num_read < 500) + goto out; + + if (piximage) { + gkrellm_destroy_piximage(piximage); + g_free(pixbuf); + } + PRINTF("%s: creating new piximage\n", __func__); + piximage = gkrellm_piximage_new_from_file(filename); + if (!piximage) { + PRINTF("%s: can not load image\n", __func__); + goto out; + } + pixbuf = gkrellm_scale_piximage_to_pixbuf(piximage, width, width); +out: + g_free(filename); + if (pipe) + pclose(pipe); + if (out) + close(out); +} + +static void create_tab(GtkWidget *tab_vbox) +{ + GtkTextBuffer *textbuf; + GtkWidget *text; + GtkWidget *scrolled, *vbox = tab_vbox; + + PRINTF("%s: \n", __func__); + + scrolled = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + textbuf = gtk_text_buffer_new(NULL); + gtk_text_buffer_set_text(textbuf, info_text, -1); + text = gtk_text_view_new_with_buffer(textbuf); + gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE); + gtk_container_add(GTK_CONTAINER(scrolled), text); + gtk_box_pack_start(GTK_BOX(vbox), scrolled, TRUE, TRUE, 0); +} + +static gboolean song_change(GIOChannel *channel, GIOCondition condition, + gpointer panel) +{ + char *str_return; + gsize length; + + PRINTF("%s: input condition %i " + "(song_change channel fd = %i)\n", __func__, condition, + g_io_channel_unix_get_fd(song_change_channel)); + if (!song_change_fd) { + PRINTF("%s: no song_change_fd\n", __func__); + goto err_out; + } + if (!(condition & (G_IO_IN | G_IO_PRI))) { + PRINTF("%s: song change pipe died\n", __func__); + song_change_fd = NULL; + wait(NULL); + return TRUE; + goto err_out; + } + if (!channel->is_readable) { + PRINTF("%s: fd not readable\n", __func__); + goto err_out; + } + PRINTF("%s: reading data\n", __func__); + if (g_io_channel_read_line(channel, &str_return, + &length, NULL, NULL) == G_IO_STATUS_NORMAL) { + PRINTF("%s: next song: %s", __func__, str_return); + g_free(str_return); + if (channel != song_change_channel) + goto err_out; + load_img(); + return TRUE; + } +err_out: + g_io_channel_unref(channel); + g_io_channel_shutdown(channel, TRUE, NULL); + return FALSE; +} + +static void create_song_change(void) +{ + if (song_change_fd) + return; + gettimeofday(&sc_open_time, NULL); + PRINTF("%s: para_client sc\n", __func__); + song_change_fd = popen("para_client sc", "r"); + if (!song_change_fd) { + PRINTF("%s: para_client sc failed\n", __func__); + return; + } + song_change_channel = g_io_channel_unix_new(fileno(song_change_fd)); + g_io_channel_set_close_on_unref(song_change_channel, TRUE); + g_io_add_watch(song_change_channel, + G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, + &song_change, panel); +} + +static void update_para_ctrl(void) +{ + GdkDrawable *drawable = panel->drawing_area->window; + gint width = gkrellm_chart_width(); + + if (!drawable) + PRINTF("%s: No drawable\n", __func__); + else + gkrellm_paste_pixbuf(pixbuf, drawable, 0, 0, width, width); + if (!song_change_fd) { + struct timeval now; + PRINTF("%s: no song_change_fd\n", __func__); + + gettimeofday(&now, NULL); + if (now.tv_sec > sc_open_time.tv_sec + 5) + create_song_change(); + } +} + +static void destroy_panel(void) +{ + PRINTF("%s: \n", __func__); + if (!panel) + return; + PRINTF("%s: destroying panel\n", __func__); + gkrellm_panel_destroy(panel); + panel = NULL; +} + +#if 0 +static void destroy_song_change(void) +{ + PRINTF("%s: \n", __func__); + if (!song_change_fd) + return; +} +#endif +static void destroy_all(void) +{ +// destroy_song_change(); + destroy_panel(); +} + +static void create_para_ctrl(GtkWidget *vbox, gint first_create) +{ + gint style_id = gkrellm_lookup_meter_style_id(UPTIME_STYLE_NAME); + GkrellmStyle *style = gkrellm_meter_style(style_id); + gint width = gkrellm_chart_width(); + + destroy_all(); + create_song_change(); + panel = gkrellm_panel_new0(); + gkrellm_panel_configure(panel, NULL, style); + gkrellm_panel_configure_set_height(panel, width); + PRINTF("%s: creating panel\n", __func__); + gkrellm_panel_create(vbox, &monitor, panel); + make_button(&prev_button, 1); +} + +static void apply_config(void) +{ + PRINTF("%s: \n", __func__); +} + +static void load_config(gchar * arg) +{ + PRINTF("%s: \n", __func__); + +} + +static void save_config(FILE * f) +{ + PRINTF("%s: \n", __func__); +} + +GkrellmMonitor *gkrellm_init_plugin(void) +{ + return &monitor; +} diff --git a/list.h b/list.h new file mode 100644 index 00000000..0080c80c --- /dev/null +++ b/list.h @@ -0,0 +1,172 @@ +/* + * Copied from the Linux kernel source tree, version 2.6.13. + * + * Licensed under the GPL v2 as per the whole kernel source tree. + * + */ + +#include /* offsetof */ +#ifndef _LINUX_LIST_H +#define _LINUX_LIST_H + +#define container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) + +/* + * These are non-NULL pointers that will result in page faults + * under normal circumstances, used to verify that nobody uses + * non-initialized list entries. + */ +#define LIST_POISON1 ((void *) 0x00100100) +#define LIST_POISON2 ((void *) 0x00200200) + +/* + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when + * manipulating whole lists rather than single entries, as + * sometimes we already know the next/prev entries and we can + * generate better code by using them directly rather than + * using the generic single-entry routines. + */ + +struct list_head { + struct list_head *next, *prev; +}; + +#define INIT_LIST_HEAD(ptr) do { \ + (ptr)->next = (ptr); (ptr)->prev = (ptr); \ +} while (0) + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_add(struct list_head *new, + struct list_head *prev, + struct list_head *next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +/** + * list_add - add a new entry + * @new: new entry to be added + * @head: list head to add it after + * + * Insert a new entry after the specified head. + * This is good for implementing stacks. + */ +static inline void list_add(struct list_head *new, struct list_head *head) +{ + __list_add(new, head, head->next); +} + +/** + * list_add_tail - add a new entry + * @new: new entry to be added + * @head: list head to add it before + * + * Insert a new entry before the specified head. + * This is useful for implementing queues. + */ +static inline void list_add_tail(struct list_head *new, struct list_head *head) +{ + __list_add(new, head->prev, head); +} + +/* + * Delete a list entry by making the prev/next entries + * point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_del(struct list_head * prev, struct list_head * next) +{ + next->prev = prev; + prev->next = next; +} + +/** + * list_del - deletes entry from list. + * @entry: the element to delete from the list. + * Note: list_empty on entry does not return true after this, the entry is + * in an undefined state. + */ +static inline void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + entry->next = LIST_POISON1; + entry->prev = LIST_POISON2; +} + +/** + * list_move - delete from one list and add as another's head + * @list: the entry to move + * @head: the head that will precede our entry + */ +static inline void list_move(struct list_head *list, struct list_head *head) +{ + __list_del(list->prev, list->next); + list_add(list, head); +} + +/** + * list_empty - tests whether a list is empty + * @head: the list to test. + */ +static inline int list_empty(const struct list_head *head) +{ + return head->next == head; +} + +/** + * list_entry - get the struct for this entry + * @ptr: the &struct list_head pointer. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + */ +#define list_entry(ptr, type, member) \ + container_of(ptr, type, member) + +/** + * list_for_each_safe - iterate over a list safe against removal of list entry + * @pos: the &struct list_head to use as a loop counter. + * @n: another &struct list_head to use as temporary storage + * @head: the head for your list. + */ +#define list_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) + +/** + * list_for_each_entry - iterate over list of given type + * @pos: the type * to use as a loop counter. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry(pos, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +/** + * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry + * @pos: the type * to use as a loop counter. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_safe(pos, n, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member), \ + n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) +#endif /* _LIST_H */ diff --git a/mp3.c b/mp3.c new file mode 100644 index 00000000..c2d0f63e --- /dev/null +++ b/mp3.c @@ -0,0 +1,504 @@ +/* + * Copyright (C) 2003-2006 Andre Noll + * + * 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 , which in turn is based + * in part on + * + * * MP3Info 0.5 by Ricardo Cerqueira + * * MP3Stat 0.9 by Ed Sweetman and + * Johannes Overmann + */ + +#include "server.cmdline.h" +#include "server.h" +#include "afs.h" +#include "gcc-compat.h" +#include "error.h" + +/** \cond some defines and structs which are only used in this file */ + +/* + * MIN_CONSEC_GOOD_FRAMES defines how many consecutive valid MP3 frames we need + * to see before we decide we are looking at a real MP3 file + */ +#define MIN_CONSEC_GOOD_FRAMES 3 + +#define FRAME_HEADER_SIZE 4 +#define MIN_FRAME_SIZE 21 + +struct mp3header { + unsigned long sync; + unsigned int version; + unsigned int layer; + unsigned int crc; + unsigned int bitrate; + unsigned int freq; + unsigned int padding; + unsigned int mode; + unsigned int copyright; + unsigned int original; + unsigned int emphasis; +}; + +struct id3tag { + char title[31]; + char artist[31]; + char album[31]; + char year[5]; + char comment[31]; +}; + +struct mp3info { + char *filename; + FILE *file; + struct mp3header header; + int id3_isvalid; + struct id3tag id3; + int vbr; + long unsigned br_average; + long unsigned seconds; + int frames; + int freq; +}; + +/** \endcond */ +static int frequencies[3][4] = { + {22050,24000,16000,50000}, /* MPEG 2.0 */ + {44100,48000,32000,50000}, /* MPEG 1.0 */ + {11025,12000,8000,50000} /* MPEG 2.5 */ +}; + +static int mp3info_bitrate[2][3][14] = { +{ /* MPEG 2.0 */ + {32,48,56,64,80,96,112,128,144,160,176,192,224,256}, /* layer 1 */ + {8,16,24,32,40,48,56,64,80,96,112,128,144,160}, /* layer 2 */ + {8,16,24,32,40,48,56,64,80,96,112,128,144,160} /* layer 3 */ +}, + +{ /* MPEG 1.0 */ + {32,64,96,128,160,192,224,256,288,320,352,384,416,448}, /* layer 1 */ + {32,48,56,64,80,96,112,128,160,192,224,256,320,384}, /* layer 2 */ + {32,40,48,56,64,80,96,112,128,160,192,224,256,320} /* layer 3 */ +} +}; + +static int frame_size_index[] = {24000, 72000, 72000}; +static char *mode_text[] = {"stereo", "joint stereo", "dual channel", "mono", "invalid"}; + +static struct mp3info mp3; +static char mp3buf[8192]; +static int chunk_size; +static struct audio_format *af; + +static __must_check int para_fread(void *ptr, size_t size, size_t nmemb, FILE *stream) +{ + size_t res = fread(ptr, size, nmemb, stream); + if (res == nmemb) + return size * nmemb; + if (feof(stream)) + return 0; + return -E_FREAD; +} + +static int header_frequency(struct mp3header *h) +{ + if (h->version > 2 || h->freq > 3) + return -E_HEADER_FREQ; + return frequencies[h->version][h->freq]; +} + +static char *header_mode(struct mp3header *h) +{ + if (h->mode > 4) + h->mode = 4; /* invalid */ + return mode_text[h->mode]; +} +static int header_bitrate(struct mp3header *h) +{ + if (h->layer > 3 || h->bitrate > 14) + return -E_HEADER_BITRATE; + return mp3info_bitrate[h->version & 1][3 - h->layer][h->bitrate - 1]; +} + +static int frame_length(struct mp3header *header) +{ + int hb, hf = header_frequency(header); + + if (hf < 0) + return hf; + hb = header_bitrate(header); + if (hb < 0) + return hb; + if (header->sync != 0xFFE || header->layer > 3) + return -E_FRAME; + return frame_size_index[3 - header->layer] * + ((header->version & 1) + 1) * hb / hf + + header->padding; +} + +static void write_info_str(char *info_str) +{ + int v = mp3.id3_isvalid; + + snprintf(info_str, MMD_INFO_SIZE, + "audio_file_info1:%d x %lu, %lu kbit/s (%cbr) %i KHz %s\n" + "audio_file_info2:%s, by %s\n" + "audio_file_info3:A: %s, Y: %s, C: %s\n", + mp3.frames, + af->chunk_tv.tv_usec, + mp3.br_average, + mp3.vbr? 'v' : 'c', + mp3.freq / 1000, + header_mode(&mp3.header), + v && *mp3.id3.title? mp3.id3.title : "(title tag not set)", + v && *mp3.id3.artist? mp3.id3.artist : "(artist tag not set)", + v && *mp3.id3.album? mp3.id3.album : "(album tag not set)", + v && *mp3.id3.year? mp3.id3.year : "????", + v && *mp3.id3.comment? mp3.id3.comment : "(comment tag not set)" + ); +} + +/* + * Remove trailing whitespace from the end of a string + */ +static char *unpad(char *string) +{ + char *pos = string + strlen(string) - 1; + while (isspace(pos[0])) + (pos--)[0] = 0; + return string; +} + +static int compare_headers(struct mp3header *h1,struct mp3header *h2) +{ + if ((*(uint*)h1) == (*(uint*)h2)) + return 1; + if ((h1->version == h2->version) && + (h1->layer == h2->layer) && + (h1->crc == h2->crc) && + (h1->freq == h2->freq) && + (h1->mode == h2->mode) && + (h1->copyright == h2->copyright) && + (h1->original == h2->original) && + (h1->emphasis == h2->emphasis)) + return 1; + else + return 0; +} + +/** + * get next MP3 frame header. + * + * \param stream to read the header from + * \param header structure that gets filled in by get_header() + * + * \return On success, the header frame length is returned. A return value of + * zero means that we did not retrieve a valid frame header, and a negative + * return value indicates an error. + */ +static int get_header(FILE *file, struct mp3header *header) +{ + unsigned char buffer[FRAME_HEADER_SIZE]; + int fl, ret; + + if (!file || !header) + return -E_MP3_NO_FILE; + ret = para_fread(buffer, FRAME_HEADER_SIZE, 1, file); + if (ret < FRAME_HEADER_SIZE) { + header->sync = 0; + return ret < 0? ret : 0; + } + header->layer = (buffer[1] >> 1) & 3; + header->sync = (((int)buffer[0]<<4) | ((int)(buffer[1]&0xE0)>>4)); + if (buffer[1] & 0x10) + header->version = (buffer[1] >> 3) & 1; + else + header->version = 2; + if ((header->sync != 0xFFE) || (header->layer != 1)) { + header->sync = 0; +// PARA_DEBUG_LOG("%s: header not found\n", __func__); + return 0; + } + header->crc = buffer[1] & 1; + header->bitrate = (buffer[2] >> 4) & 0x0F; +// PARA_DEBUG_LOG("%s: found header, bitrate: %u\n", __func__, +// header->bitrate); + header->freq = (buffer[2] >> 2) & 0x3; + header->padding = (buffer[2] >>1) & 0x1; + header->mode = (buffer[3] >> 6) & 0x3; + fl = frame_length(header); + return (fl >= MIN_FRAME_SIZE)? fl : -E_FRAME_LENGTH; +} + +/** + * find the next mp3 header + * + * \return On success, the length of the next frame header. If the end of the + * file was reached, the function returns zero. On errors, a negative value is + * returned. + * + */ +static int mp3_seek_next_header(void) +{ + int k, l = 0, c, first_len; + struct mp3header h, h2; + long valid_start = 0; + + while (1) { + while ((c = fgetc(mp3.file)) != 255 && (c != EOF)) + ; /* nothing */ + if (c != 255) + return 0; + ungetc(c, mp3.file); + valid_start = ftell(mp3.file); + first_len = get_header(mp3.file, &h); + if (first_len <= 0) + continue; + if (fseek(mp3.file, first_len - FRAME_HEADER_SIZE, SEEK_CUR) < 0) + return -E_FSEEK; + for (k = 1; k < MIN_CONSEC_GOOD_FRAMES; k++) { + if ((l = get_header(mp3.file, &h2)) <= 0) + break; + if (!compare_headers(&h, &h2)) + break; + fseek(mp3.file, l - FRAME_HEADER_SIZE, SEEK_CUR); + } + if (k == MIN_CONSEC_GOOD_FRAMES) { + fseek(mp3.file, valid_start, SEEK_SET); + memcpy(&(mp3.header), &h2, sizeof(struct mp3header)); + return first_len; + } + } +} + +static int mp3_get_id3(void) +{ + char fbuf[4]; + + mp3.id3_isvalid = 0; + mp3.id3.title[0] = '\0'; + mp3.id3.artist[0] = '\0'; + mp3.id3.album[0] = '\0'; + mp3.id3.comment[0] = '\0'; + mp3.id3.year[0] = '\0'; + if (fseek(mp3.file, -128, SEEK_END)) + return -E_FSEEK; + if (para_fread(fbuf, 1, 3, mp3.file) < 0) + return -E_FREAD; + fbuf[3] = '\0'; + if (strcmp("TAG", fbuf)) { + PARA_INFO_LOG("%s", "no id3 tag\n"); + return 0; + } + if (fseek(mp3.file, -125, SEEK_END) < 0) + return -E_FSEEK; + if (para_fread(mp3.id3.title, 1, 30, mp3.file) != 30) + return -E_FREAD; + mp3.id3.title[30] = '\0'; + if (para_fread(mp3.id3.artist, 1, 30, mp3.file) != 30) + return -E_FREAD; + mp3.id3.artist[30] = '\0'; + if (para_fread(mp3.id3.album, 1, 30, mp3.file) != 30) + return -E_FREAD; + mp3.id3.album[30] = '\0'; + if (para_fread(mp3.id3.year, 1, 4, mp3.file) != 4) + return -E_FREAD; + mp3.id3.year[4] = '\0'; + if (para_fread(mp3.id3.comment, 1, 30, mp3.file) != 30) + return -E_FREAD; + mp3.id3.comment[30] = '\0'; + mp3.id3_isvalid = 1; + unpad(mp3.id3.title); + unpad(mp3.id3.artist); + unpad(mp3.id3.album); + unpad(mp3.id3.year); + unpad(mp3.id3.comment); + return 1; +} + +static int find_valid_start(void) +{ + int frame_len; + + if (!mp3.file) + return -E_MP3_NO_FILE; + frame_len = get_header(mp3.file, &mp3.header); + if (frame_len < 0) + return frame_len; + if (!frame_len) { + frame_len = mp3_seek_next_header(); + if (frame_len <= 0) + return frame_len; + } else + if (fseek(mp3.file, -FRAME_HEADER_SIZE, SEEK_CUR) < 0) + return -E_FSEEK; + if (frame_len <= 1) /* FRAME_HEADER_SIZE? */ + return -E_FRAME_LENGTH; + return frame_len; +} + +static int mp3_read_info(void) +{ + long fl_avg = 0, freq_avg = 0, br_avg = 0, fcount = 0; + int ret, len = 0, old_br = -1; + struct timeval total_time = {0, 0}; + + ret = mp3_get_id3(); + if (ret < 0) + return ret; + rewind(mp3.file); + mp3.vbr = 0; + mp3.freq = 0; + while (1) { + int freq, br, fl; + struct timeval tmp, cct; /* current chunk time */ + if (len > 0) + if (fseek(mp3.file, len, SEEK_CUR) < 0) + return -E_FSEEK; + len = find_valid_start(); + if (len <= 0) + break; + freq = header_frequency(&mp3.header); + br = header_bitrate(&mp3.header); + fl = frame_length(&mp3.header); + if (freq < 0 || br < 0 || fl < 0) + continue; + tmp.tv_sec = fl; + tmp.tv_usec = 0; + tv_divide(br * 125, &tmp, &cct); + tv_add(&cct, &total_time, &tmp); + total_time = tmp; +// PARA_DEBUG_LOG("%s: br: %d, freq: %d, fl: %d, cct: %lu\n", __func__, br, freq, fl, cct.tv_usec); + fcount++; + if (fcount == 1) { + freq_avg = freq; + br_avg = br; + old_br = br; + fl_avg = fl; + continue; + } + freq_avg += (freq - freq_avg) / (fcount + 1); + fl_avg += (fl - fl_avg) / (fcount + 1); + br_avg += (br - br_avg) / (fcount + 1); + if (old_br != br) + mp3.vbr = 1; + old_br = br; + } + if (!fcount || !freq_avg || !br_avg) + return -E_MP3_INFO; + mp3.br_average = br_avg; + mp3.freq = freq_avg; + mp3.frames = fcount; + mp3.seconds = (tv2ms(&total_time) + 500) / 1000; + tv_divide(fcount, &total_time, &af->chunk_tv); + rewind(mp3.file); + PARA_DEBUG_LOG("chunk_time: %lu\n", af->chunk_tv.tv_usec); + tv_scale(10, &af->chunk_tv, &af->eof_tv); + return 1; +} + +/* + * Read mp3 information from audio file + */ +static int mp3_get_file_info(FILE *audio_file, char *info_str, + long unsigned *frames, int *seconds) +{ + int ret; + + if (!audio_file) + return -E_MP3_NO_FILE; + mp3.file = audio_file; + ret = mp3_read_info(); + if (ret < 0) { + mp3.file = NULL; + return ret; + } + write_info_str(info_str); + *frames = mp3.frames; + *seconds = mp3.seconds; + if (*seconds < 2 || !*frames) + return -E_MP3_INFO; + return 1; +} + +static int mp3_reposition_stream(long unsigned new_frame) +{ + int count = 0, len; + + PARA_DEBUG_LOG("jmp to frame %lu/%i\n", new_frame, mp3.frames); + rewind(mp3.file); + while (count < new_frame && (len = find_valid_start()) > 0) { +// PARA_DEBUG_LOG("%s: jmp to frame %d\n", __func__, count); + if (fseek(mp3.file, len, SEEK_CUR) < 0) + return -E_FSEEK; + count++; + } + if (count != new_frame) { + rewind(mp3.file); + return -E_MP3_REPOS; + } + return 1; +} + +static int mp3_read_next_chunk(void) +{ + int len = find_valid_start(); + + if (len <= 0) { + if (len < 0) + PARA_ERROR_LOG("invalid frame len (%d)\n", len); + return len; + } + chunk_size = para_fread(mp3buf, len, 1, mp3.file); + if (len != chunk_size) + PARA_DEBUG_LOG("short read (%d/%d)\n", chunk_size, len); + return chunk_size; +} + +static char *mp3_read_chunk(__unused long unsigned chunk_num, ssize_t *len) +{ + *len = mp3_read_next_chunk(); + if (*len <= 0) + return NULL; + return mp3buf; +} + +static void mp3_close_audio_file(void) +{ + if (!mp3.file) + return; + fclose(mp3.file); + mp3.file = NULL; +} + +void mp3_init(void *p) +{ + af = p; + af->get_file_info = mp3_get_file_info; + af->reposition_stream = mp3_reposition_stream; + af->read_chunk = mp3_read_chunk; + af->close_audio_file = mp3_close_audio_file; + af->get_header_info = NULL; + /* eof_tv gets overwritten in mp3_get_file_info() */ + af->eof_tv.tv_sec = 0; + af->eof_tv.tv_usec = 100 * 1000; +} diff --git a/mp3dec.c b/mp3dec.c new file mode 100644 index 00000000..0100d04e --- /dev/null +++ b/mp3dec.c @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2005-2006 Andre Noll + * + * 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 +#include "string.h" + +/** the output buffer size */ +#define MP3_OUTBUF_SIZE 128 * 1024 + +/** \cond a helper macro */ +#define MAD_TO_SHORT(f) (f) >= MAD_F_ONE? SHRT_MAX :\ + (f) <= -MAD_F_ONE? -SHRT_MAX : (signed short) ((f) >> (MAD_F_FRACBITS - 15)) +/** \endcond */ + +/** + * data specific to the mp3dec filter + * + * \sa filter, filter_node + */ +struct private_mp3dec_data { + /** information on the current mp3 stream */ + struct mad_stream stream; + /** information about the frame which is currently decoded */ + struct mad_frame frame; + /** contains the PCM output */ + struct mad_synth synth; +}; + +static ssize_t mp3dec(char *inbuffer, size_t len, struct filter_node *fn) +{ + int i, ret; + struct private_mp3dec_data *pmd = fn->private_data; + size_t copy = MIN(len, 4096); + + if (fn->loaded > fn->bufsize * 4 / 5) + return 0; + mad_stream_buffer(&pmd->stream, (unsigned char *) inbuffer, copy); + pmd->stream.error = 0; +next_frame: + ret = mad_frame_decode(&pmd->frame, &pmd->stream); + if (ret) { + if (MAD_RECOVERABLE(pmd->stream.error) || pmd->stream.error == MAD_ERROR_BUFLEN) + goto out; + PARA_ERROR_LOG("fatal: ret = %d, loaded = %d\n", ret, fn->loaded); + return -E_MAD_FRAME_DECODE; + } + mad_synth_frame(&pmd->synth, &pmd->frame); + fn->fci->samplerate = pmd->frame.header.samplerate; + fn->fci->channels = MAD_NCHANNELS(&pmd->frame.header); + + for (i = 0; i < pmd->synth.pcm.length; i++) { + /* output format: unsigned 16 bit little endian */ + signed short s = MAD_TO_SHORT(pmd->synth.pcm.samples[0][i]); + fn->buf[fn->loaded++] = s & 0xff; + fn->buf[fn->loaded++] = s >> 8; + if (MAD_NCHANNELS(&pmd->frame.header) == 2) { /* stereo */ + s = MAD_TO_SHORT(pmd->synth.pcm.samples[1][i]); + fn->buf[fn->loaded++] = s & 0xff; + fn->buf[fn->loaded++] = s >> 8; + } + if (fn->loaded != fn->bufsize) /* output buffer not full */ + continue; + PARA_ERROR_LOG("output buffer full: %d\n", fn->loaded); + return -E_MP3DEC_OVERRUN; + } + if (fn->loaded <= fn->bufsize * 4 / 5) + goto next_frame; +out: + if (pmd->stream.next_frame) { /* we still have some data */ + size_t off = pmd->stream.bufend - pmd->stream.next_frame; + PARA_DEBUG_LOG("converted %d, %d input bytes, %d output bytes\n", + len - off, off, fn->loaded); + return copy - off; + } + return copy; +} + +static void mp3dec_close(struct filter_node *fn) +{ + struct private_mp3dec_data *pmd = fn->private_data; + + mad_synth_finish(&pmd->synth); + mad_frame_finish(&pmd->frame); + mad_stream_finish(&pmd->stream); + + free(fn->buf); + fn->buf = NULL; + free(pmd); + fn->private_data = NULL; +} + +static void mp3dec_open(struct filter_node *fn) +{ + fn->private_data = para_calloc(sizeof(struct private_mp3dec_data)); + struct private_mp3dec_data *pmd = fn->private_data; + + mad_stream_init(&pmd->stream); + mad_frame_init(&pmd->frame); + mad_synth_init(&pmd->synth); + fn->loaded = 0; + fn->bufsize = MP3_OUTBUF_SIZE; + fn->buf = para_calloc(fn->bufsize); +} +/** + * the init function of the mp3dec filter + * + * \sa filter::init + */ +void mp3dec_init(struct filter *f) +{ + f->open = mp3dec_open; + f->convert = mp3dec; + f->close = mp3dec_close; +} diff --git a/mysql.c b/mysql.c new file mode 100644 index 00000000..b8fa02dc --- /dev/null +++ b/mysql.c @@ -0,0 +1,2539 @@ +/* + * Copyright (C) 1999-2006 Andre Noll + * + * 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 +#include +#include "error.h" +#include "net.h" +#include "string.h" + +extern struct gengetopt_args_info conf; +/** pointer to the shared memory area */ +extern struct misc_meta_data *mmd; + +static void *mysql_ptr = NULL; + + +static int com_cam(int, int, char **); +static int com_cdb(int, int, char **); +static int com_cs(int, int, char **); +static int com_da(int, int, char **); +static int com_hist(int, int, char **); +static int com_info(int, int, char **); +static int com_laa(int, int, char **); +static int com_last(int, int, char **); +static int com_ls(int, int, char **); +static int com_mbox(int, int, char **); +static int com_mv(int, int, char **); +static int com_na(int, int, char **); +static int com_pic(int, int, char **); +static int com_picch(int, int, char **); +static int com_picdel(int, int, char **); +static int com_piclist(int, int, char **); +static int com_ps(int, int, char **); +static int com_rm_ne(int, int, char **); +static int com_sa(int, int, char **); +static int com_set(int, int, char **); +static int com_sl(int, int, char **); +static int com_stradd_picadd(int, int, char **); +static int com_streams(int, int, char **); +static int com_strdel(int, int, char **); +static int com_strq(int, int, char **); +static int com_summary(int, int, char **); +static int com_upd(int, int, char **); +static int com_us(int, int, char **); +static int com_verb(int, int, char **); +static int com_vrfy(int, int, char **); + +static struct server_command cmds[] = { +{ +.name = "cam", +.handler = com_cam, +.perms = DB_READ|DB_WRITE, +.description = "copy all metadata", +.synopsis = "cam source dest1 [dest2 ...]", +.help = + +"Copy attributes and other meta data from source file to destination\n" +"file(s). Useful for files that have been renamed.\n" + +}, +{ +.name = "cdb", +.handler = com_cdb, +.perms = DB_READ|DB_WRITE, +.description = "create database", +.synopsis = "cdb [name]", +.help = + +"\tCreate database name containing the initial columns for basic\n" +"\tinteroperation with server. This command has to be used only once\n" +"\twhen you use the mysql database tool for the very first time.\n" +"\n" +"\tThe optional name defaults to 'paraslash' if not given.\n" + +}, +{ +.name = "clean", +.handler = com_vrfy, +.perms = DB_READ | DB_WRITE, +.description = "nuke invalid entries in database", +.synopsis = "clean", +.help = + +"If the vrfy command shows you any invalid entries in your database,\n" +"you can get rid of them with clean. Always run 'upd' and 'vrfy'\n" +"before running this command. Use with caution!\n" + +}, +{ +.name = "cs", +.handler = com_cs, +.perms = AFS_WRITE | DB_READ | DB_WRITE, +.description = "change stream", +.synopsis = "cs [s]", +.help = + +"Selects stream s or prints current stream when s was not given.\n" + +}, +{ +.name = "csp", +.handler = com_cs, +.perms = AFS_WRITE | DB_READ, +.description = "change stream and play", +.synopsis = "csp s", +.help = + +"Select stream s and start playing. If this results in a stream-change,\n" +"skip rest of current audio file.\n" + +}, +{ +.name = "da", +.handler = com_da, +.perms = DB_READ | DB_WRITE, +.description = "drop attribute from database", +.synopsis = "da att", +.help = + +"Use with caution. All info on attribute att will be lost.\n" + +}, +{ +.name = "hist", +.handler = com_hist, +.perms = DB_READ, +.description = "print history", +.synopsis = "hist", +.help = + +"Print list of all audio files together with number of days since each\n" +"file was last played.\n" + +}, +{ +.name = "info", +.handler = com_info, +.perms = DB_READ, +.description = "print database info", +.synopsis = "info [af]", +.help = + +"print database informations for audio file af. Current audio file is\n" +"used if af is not given.\n" + +}, +{ +.name = "la", +.handler = com_info, +.perms = DB_READ, +.description = "list attributes", +.synopsis = "la [af]", +.help = + +"List attributes of audio file af or of current audio file when invoked\n" +"without arguments.\n" + +}, +{ +.name = "laa", +.handler = com_laa, +.perms = DB_READ, +.description = "list available attributes", +.synopsis = "laa", +.help = + +"What should I say more?\n" + +}, +{ +.name = "last", +.handler = com_last, +.perms = DB_READ, +.description = "print list of audio files, ordered by lastplayed time", +.synopsis = "last [n]", +.help = + +"The optional number n defaults to 10 if not specified.\n" + +}, +{ +.name = "ls", +.handler = com_ls, +.perms = DB_READ, +.description = "list all audio files that match a LIKE pattern", +.synopsis = "ls [pattern]", +.help = + +"\tIf pattern was not given, print list of all audio files known\n" +"\tto the mysql database tool. See the documentation of mysql\n" +"\tfor the definition of LIKE patterns.\n" + +}, +{ +.name = "mbox", +.handler = com_mbox, +.perms = DB_READ, +.description = "dump audio file list in mbox format", +.synopsis = "mbox [p]", +.help = + +"\tDump list of audio files in mbox format (email) to stdout. If\n" +"\tthe optional pattern p is given, only those audio files,\n" +"\twhose basename match p are going to be included. Otherwise,\n" +"\tall files are selected.\n" +"\n" +"EXAMPLE\n" +"\tThe mbox command can be used together with your favorite\n" +"\tmailer (this example uses mutt) for browsing the audio file\n" +"\tcollection:\n" +"\n" +"\t\tpara_client mbox > ~/para_mbox\n" +"\n" +"\t\tmutt -F ~/.muttrc.para -f ~/para_mbox\n" +"\n" +"\tFor playlists, you can use mutt's powerful pattern matching\n" +"\tlanguage to select files. If you like to tag all files\n" +"\tcontaining the pattern 'foo', type 'T', then '~s foo'.\n" +"\n" +"\tWhen ready with the list, type ';|' (i.e., hit the semicolon\n" +"\tkey to apply the next mutt command to all tagged messages,\n" +"\tthen the pipe key) to pipe the selected \"mails\" to a\n" +"\tsuitable script which adds a paraslash stream where exactly\n" +"\tthese files are admissable or does whatever thou wilt.\n" + +}, +{ +.name = "mv", +.handler = com_mv, +.perms = DB_READ | DB_WRITE, +.description = "rename entry in database", +.synopsis = "mv oldname newname", +.help = + +"Rename oldname to newname. This updates the data table to reflect the\n" +"new name. All internal data (numplayed, lastplayed, picid,..) is kept.\n" +"If newname is a full path, the dir table is updated as well.\n" + +}, +{ +.name = "na", +.handler = com_na, +.perms = DB_READ | DB_WRITE, +.description = "add new attribute to database", +.synopsis = "na att", +.help = + +"This adds a column named att to your mysql database. att should only\n" +"contain letters and numbers, in paricular, '+' and '-' are not allowed.\n" + +}, +{ +.name = "ne", +.handler = com_rm_ne, +.perms = DB_READ | DB_WRITE, +.description = "add new database entries", +.synopsis = "ne file1 [file2 [...]]", +.help = + +"Add the given filename(s) to the database, where file1,... must\n" +"be full path names. This command might be much faster than 'upd'\n" +"if the number of given files is small.\n" + +}, +{ +.name = "ns", +.handler = com_ps, +.perms = AFS_WRITE | DB_READ | DB_WRITE, +.description = "change to next stream", +.synopsis = "ns", +.help = + +"Cycle forwards through stream list.\n" + +}, +{ +.name = "pic", +.handler = com_pic, +.perms = DB_READ, +.description = "get picture by name or by identifier", +.synopsis = "pic [name]", +.help = + +"\tDump jpg image that is associated to given audio file (current\n" +"\taudio file if not specified) to stdout. If name starts with\n" +"\t'#' it is interpreted as an identifier instead and the picture\n" +"\thaving that identifier is dumped to stdout.\n" +"\n" +"EXAMPLE\n" +"\n" +"\tpara_client pic '#123' > pic123.jpg\n" + +}, +{ +.name = "picadd", +.handler = com_stradd_picadd, +.perms = DB_READ | DB_WRITE, +.description = "add picture to database", +.synopsis = "picadd [picname]", +.help = + +"\tRead jpeg file from stdin and store it as picname in database.\n" +"\n" +"EXAMPLE\n" +"\n" +"\tpara_client picadd foo.jpg < foo.jpg\n" + +}, +{ +.name = "picass", +.handler = com_set, +.perms = DB_READ | DB_WRITE, +.description = "associate a picture to file(s)", +.synopsis = "picass pic_id file1 [file2...]", +.help = + +"Associate the picture given by pic_id to all given files.\n" + +}, +{ +.name = "picch", +.handler = com_picch, +.perms = DB_READ | DB_WRITE, +.description = "change name of picture", +.synopsis = "picch id new_name", +.help = + +"Asign new_name to picture with identifier id.\n" + +}, +{ +.name = "picdel", +.handler = com_picdel, +.perms = DB_READ | DB_WRITE, +.description = "delete picture from database", +.synopsis = "picdel id1 [id2...]", +.help = + +"Delete each given picture from database.\n" + +}, +{ +.name = "piclist", +.handler = com_piclist, +.perms = DB_READ, +.description = "print list of pictures", +.synopsis = "piclist", +.help = + +"Print id, name and length of each picture contained in the database.\n" + +}, +{ +.name = "ps", +.handler = com_ps, +.perms = AFS_WRITE | DB_READ | DB_WRITE, +.description = "change to previous stream", +.synopsis = "ps", +.help = + +"Cycle backwards through stream list.\n" + +}, +{ +.name = "rm", +.handler = com_rm_ne, +.perms = DB_READ | DB_WRITE, +.description = "remove entries from database", +.synopsis = "rm name1 [name2 [...]]", +.help = + +"Remove name1, name2, ... from the data table. Use with caution\n" + +}, +{ +.name = "sa", +.handler = com_sa, +.perms = DB_READ | DB_WRITE, +.description = "set/unset attributes", +.synopsis = "sa at1<'+' | '-'> [at2<'+' | '-'> ] [af1 ...]", +//.synopsis = "foo", +.help = + +"Set ('+') or unset ('-') attribute at1, at2 etc. for given list of\n" +"audio files. If no audio files were given the current audio file is\n" +"used. Example:\n" +"\n" +"sa rock+ punk+ classic- LZ__Waldsterben.mp3\n" +"\n" +"sets the 'rock' and the 'punk' attribute but unsets the 'classic'\n" +"attribute.\n" + +}, +{ +.name = "skip", +.handler = com_sl, +.perms = DB_READ | DB_WRITE, +.description = "skip subsequent audio files(s)", +.synopsis = "skip n [s]", +.help = + +"Skip the next n audio files of stream s. This is equivalent to the\n" +"command 'sl n s', followed by 'us name' for each name the output of sl.\n" + +}, +{ +.name = "sl", +.handler = com_sl, +.perms = DB_READ, +.description = "print score list", +.synopsis = "sl n [s]", +.help = + +"Print sorted list of maximal n lines. Each line is an admissible entry\n" +"with respect to stream s. The list is sorted by score-value which is\n" +"given by the definition of s. If s is not given, the current stream\n" +"is used. Example:\n" +"\n" +" sl 1\n" +"\n" +"shows you the audio file the server would select right now.\n" + +}, +{ +.name = "snp", +.handler = com_set, +.perms = DB_READ | DB_WRITE, +.description = "set numplayed", +.synopsis = "snp number af1 [af2 ...]", +.help = + +"Update the numplayed field in the data table for all given audio files.\n" + +}, +{ +.name = "stradd", +.handler = com_stradd_picadd, +.perms = DB_READ | DB_WRITE, +.description = "add stream", +.synopsis = "stradd s", +.help = + +"Add stream s to the list of available streams. The stream definition\n" +"for s is read from stdin and is then sent to para_server. Example:\n" +"\n" +" echo 'deny: NAME_LIKE(%Madonna%)' | para_client stradd no_madonna\n" +"\n" +"adds the new stream 'no_madonna' to the list of available streams. A given\n" +"audio file is admissible for this stream iff its basename does not contain the\n" +"string 'Madonna'.\n" + + +}, +{ +.name = "strdel", +.handler = com_strdel, +.perms = DB_READ | DB_WRITE, +.description = "delete stream", +.synopsis = "strdel s", +.help = + +"Remove stream s from database.\n" + +}, +{ +.name = "streams", +.handler = com_streams, +.perms = DB_READ, +.description = "list streams", +.synopsis = "streams", +.help = + +"Print list of available streams. Use 'cs' to switch to any of these.\n" + +}, +{ +.name = "strq", +.handler = com_strq, +.perms = DB_READ, +.description = "query stream definition", +.synopsis = "strq [s]", +.help = + +"Print definition of stream s to stdout. Use current stream if s was\n" +"not given.\n" + +}, +{ +.name = "summary", +.handler = com_summary, +.perms = DB_READ, +.description = "list attributes", +.synopsis = "summary", +.help = + +"\tPrint a list of attributes together with number of audio\n" +"\tfiles having that attribute set.\n" + +}, +{ +.name = "upd", +.handler = com_upd, +.perms = DB_READ | DB_WRITE, +.description = "update database", +.synopsis = "upd", +.help = + +"This command uses the --audio_file_dir option of para_server to locate\n" +"your audio files. New files are then added to the mysql database. Use\n" +"this command if you got new files or if you have moved some files\n" +"around.\n" + +}, +{ +.name = "us", +.handler = com_us, +.perms = DB_READ | DB_WRITE, +.description = "update lastplayed time", +.synopsis = "us name", +.help = + +"Update lastplayed time without actually playing the thing.\n" + +}, +{ +.name = "verb", +.handler = com_verb, +.perms = DB_READ | DB_WRITE, +.description = "send verbatim sql query", +.synopsis = "verb cmd", +.help = + +"Send cmd to mysql server. For expert/debugging only. Note that cmd\n" +"usually must be escaped. Use only if you know what you are doing!\n" + +}, +{ +.name = "vrfy", +.handler = com_vrfy, +.perms = DB_READ, +.description = "list invalid entries in database", +.synopsis = "vrfy", +.help = + +"Show what clean would delete. Run 'upd' before this command to make\n" +"sure your database is up to date.\n" + +}, +{ +.name = NULL, +} +}; + +static struct para_macro macro_list[] = { + { .name = "IS_N_SET", + .replacement = "(data.%s != '1')" + }, { + .name = "IS_SET", + .replacement = "(data.%s = '1')" + }, { + .name = "PICID", + .replacement = "%sdata.Pic_Id" + }, { + .name = "NAME_LIKE", + .replacement = "(data.name like '%s')" + }, { + .name = "LASTPLAYED", + .replacement = "%sFLOOR((UNIX_TIMESTAMP(now())" + "-UNIX_TIMESTAMP(data.Lastplayed))/60)" + }, { + .name = "NUMPLAYED", + .replacement = "%sdata.Numplayed" + }, { + .name = NULL, + } +}; + +static int real_query(char *query) +{ + if (!mysql_ptr) + return -E_NOTCONN; + PARA_DEBUG_LOG("%s\n", query); + if (mysql_real_query(mysql_ptr, query, strlen(query))) { + PARA_ERROR_LOG("real_query error (%s)\n", + mysql_error(mysql_ptr)); + return -E_QFAILED; + } + return 1; +} + +/* + * Use open connection given by mysql_ptr to query server. Returns a + * result pointer on succes and NULL on errors + */ +static struct MYSQL_RES *get_result(char *query) +{ + void *result; + + if (real_query(query) < 0) + return NULL; + result = mysql_store_result(mysql_ptr); + if (!result) + PARA_ERROR_LOG("%s", "store_result error\n"); + return result; +} +/* + * write input from fd to dynamically allocated char array, + * but maximal max_size byte. Return size. + */ +static int fd2buf(int fd, char **buf_ptr, size_t max_size) +{ + const size_t chunk_size = 1024; + size_t size = 2048; + char *buf = para_malloc(size * sizeof(char)), *p = buf; + int ret; + + while ((ret = recv_bin_buffer(fd, p, chunk_size)) > 0) { + p += ret; + if ((p - buf) + chunk_size >= size) { + char *tmp; + + size *= 2; + if (size > max_size) { + ret = -E_TOOBIG; + goto out; + } + tmp = para_realloc(buf, size); + p = (p - buf) + tmp; + buf = tmp; + } + } + if (ret < 0) + goto out; + *buf_ptr = buf; + ret = p - buf; +out: + if (ret < 0 && buf) + free(buf); + return ret; +} + +static char *escape_blob(char* old, int size) +{ + char *new; + + if (!mysql_ptr || size < 0) + return NULL; + new = para_malloc(2 * size * sizeof(char) + 1); + mysql_real_escape_string(mysql_ptr, new, old, size); + return new; +} + +static char *escape_str(char* old) +{ + return escape_blob(old, strlen(old)); +} + +static char *escaped_basename(const char *name) +{ + char *esc, *bn = para_basename(name); + + if (!bn) + return NULL; + esc = escape_str(bn); + free(bn); + return esc; +} + +/* + * new attribute + */ +static int com_na(__unused int fd, int argc, char *argv[]) +{ + char *q; + int ret; + + if (argc < 1) + return -E_MYSQL_SYNTAX; + q = make_message("alter table data add %s char(1) " + "not null default 0", argv[1]); + ret = real_query(q); + free(q); + return ret; +} + +/* + * delete attribute + */ +static int com_da(__unused int fd, int argc, char *argv[]) +{ + char *q; + int ret; + + if (argc < 1) + return -E_MYSQL_SYNTAX; + q = make_message("alter table data drop %s", argv[1]); + ret = real_query(q); + free(q); + return ret; +} + +/* stradd/pic_add */ +static int com_stradd_picadd(int fd, int argc, char *argv[]) +{ + char *blob = NULL, *esc_blob = NULL, *q; + const char *fmt, *del_fmt; + int ret, stradd = strcmp(argv[0], "picadd"); + size_t size; + + if (argc < 1) + return -E_MYSQL_SYNTAX; + if (strlen(argv[1]) >= MAXLINE - 1) + return -E_NAMETOOLONG; + if (!mysql_ptr) + return -E_NOTCONN; + if (stradd) { + size = BLOB_SIZE; + fmt = "insert into streams (name, def) values ('%s','%s')"; + del_fmt="delete from streams where name='%s'"; + } else { + size = MEDIUM_BLOB_SIZE; + fmt = "insert into pics (name, pic) values ('%s','%s')"; + del_fmt="delete from pics where pic='%s'"; + } + q = make_message(del_fmt, argv[1]); + ret = real_query(q); + free(q); + if (ret < 0) + return ret; + if ((ret = send_buffer(fd, AWAITING_DATA_MSG) < 0)) + return ret; + if ((ret = fd2buf(fd, &blob, size)) < 0) + return ret; + PARA_DEBUG_LOG("length: %i\n", ret); + size = ret; + if (stradd) + blob[size] = '\0'; + esc_blob = escape_blob(blob, ret); + free(blob); + if (!esc_blob) + return -E_TOOBIG; + q = make_message(fmt, argv[1], esc_blob); + free(esc_blob); + ret = real_query(q); + free(q); + return ret; +} + +/* + * print results to fd + */ +static int print_results(int fd, void *result, + unsigned int top, unsigned int left, + unsigned int bottom, unsigned int right) +{ + unsigned int i,j; + int ret; + MYSQL_ROW row; + + for (i = top; i <= bottom; i++) { + row = mysql_fetch_row(result); + if (!row || !row[0]) + return -E_NOROW; + for (j = left; j <= right; j++) { + ret = send_va_buffer(fd, j == left? "%s" : "\t%s", + row[j]? row[j] : "NULL"); + if (ret < 0) + return ret; + } + ret = send_buffer(fd, "\n"); + if (ret < 0) + return ret; + } + return 0; +} + +/* + * verbatim + */ +static int com_verb(int fd, int argc, char *argv[]) +{ + void *result = NULL; + int ret; + unsigned int num_rows, num_fields; + + if (argc < 1) + return -E_MYSQL_SYNTAX; + result = get_result(argv[1]); + if (!result) + /* return success, because it's ok to have no results */ + return 1; + num_fields = mysql_field_count(mysql_ptr); + num_rows = mysql_num_rows(result); + ret = 1; + if (num_fields && num_rows) + ret = print_results(fd, result, 0, 0, num_rows - 1, + num_fields - 1); + mysql_free_result(result); + return ret; +} + +/* returns NULL on errors or if there are no atts defined yet */ +static void *get_all_attributes(void) +{ + void *result = get_result("desc data"); + unsigned int num_rows; + + if (!result) + return NULL; + num_rows = mysql_num_rows(result); + if (num_rows < 5) { + mysql_free_result(result); + return NULL; + } + mysql_data_seek(result, 4); /* skip Lastplayed, Numplayed... */ + return result; +} + +/* + * list all attributes + */ +static int com_laa(int fd, int argc, __unused char *argv[]) +{ + void *result; + int ret; + + if (argc) + return -E_MYSQL_SYNTAX; + result = get_all_attributes(); + if (!result) + return -E_NOATTS; + ret = print_results(fd, result, 0, 0, mysql_num_rows(result) - 5, 0); + mysql_free_result(result); + return ret; +} + +/* + * history + */ +static int com_hist(int fd, int argc, char *argv[]) { + int ret; + void *result = NULL; + char *q; + unsigned int num_rows; + + q = make_message("select name, to_days(now()) - to_days(lastplayed) from " + "data%s%s%s order by lastplayed", + (argc < 1)? "" : " where ", + (argc < 1)? "" : argv[1], + (argc < 1)? "" : " = '1'"); + result = get_result(q); + free(q); + if (!result) + return -E_NORESULT; + num_rows = mysql_num_rows(result); + ret = 1; + if (num_rows) + ret = print_results(fd, result, 0, 0, num_rows - 1, 1); + mysql_free_result(result); + return ret; +} + +/* + * get last num audio files + */ +static int com_last(int fd, int argc, char *argv[]) +{ + void *result = NULL; + char *q; + int num, ret; + + if (argc < 1) + num = 10; + else + num = atoi(argv[1]); + if (!num) + return -E_MYSQL_SYNTAX; + q = make_message("select name from data order by lastplayed desc " + "limit %u", num); + result = get_result(q); + free(q); + if (!result) + return -E_NORESULT; + ret = print_results(fd, result, 0, 0, mysql_num_rows(result) - 1, 0); + mysql_free_result(result); + return ret; +} + +static int com_mbox(int fd, int argc, char *argv[]) +{ + void *result; + MYSQL_ROW row; + int ret; + unsigned int num_rows, num_fields; + char *query = para_strdup("select concat('From foo@localhost ', " + "date_format(Lastplayed, '%a %b %e %T %Y'), " + "'\nReceived: from\nTo: bar\n"); + + ret = -E_NOATTS; + result = get_all_attributes(); + if (!result) + goto out; + ret = -E_NOROW; + while ((row = mysql_fetch_row(result))) { + char *tmp; + + if (!row[0]) + goto out; + tmp = make_message("%s X-Attribute-%s: ', %s, '\n", query, + row[0], row[0]); + free(query); + query = tmp; + } + query = para_strcat(query, + "From: a\n" + "Subject: " + "', name, '" + "\n\n\n" + "') from data" + ); + if (argc >= 1) { + char *tmp = make_message("%s where name LIKE '%s'", query, + argv[1]); + free(query); + query = tmp; + } + mysql_free_result(result); + ret = -E_NORESULT; + result = get_result(query); + if (!result) + goto out; + ret = -E_EMPTY_RESULT; + num_fields = mysql_field_count(mysql_ptr); + num_rows = mysql_num_rows(result); + if (!num_fields || !num_rows) + goto out; + ret = print_results(fd, result, 0, 0, num_rows - 1, num_fields - 1); +out: + free(query); + if (result) + mysql_free_result(result); + return ret; +} + +/* get attributes by name. If verbose is not 0, get_a writes a string + * into atts of the form 'att1="0",att2="1"', which is used in com_cam + * for contructing a mysql update query. + * never returns NULL in *NON VERBOSE* mode + */ +static char *get_atts(char *name, int verbose) +{ + char *atts = NULL, *buf, *ebn; + void *result = NULL, *result2 = NULL; + MYSQL_ROW row, row2; + int i, ret; + unsigned int num_fields; + + ret = -E_NOATTS; + result2 = get_all_attributes(); + if (!result2) + goto out; + ret = -E_ESCAPE; + if (!(ebn = escaped_basename(name))) + goto out; + buf = make_message("select * from data where name='%s'", ebn); + free(ebn); + ret = -E_NORESULT; + result = get_result(buf); + free(buf); + if (!result) + goto out; + ret = -E_EMPTY_RESULT; + num_fields = mysql_num_fields(result); + if (num_fields < 5) + goto out; + mysql_data_seek(result2, 4); /* skip Lastplayed, Numplayed... */ + row = mysql_fetch_row(result); + ret = -E_NOROW; + if (!row) + goto out; + for (i = 4; i < num_fields; i++) { + int is_set = row[i] && !strcmp(row[i], "1"); + row2 = mysql_fetch_row(result2); + if (!row2 || !row2[0]) + goto out; + if (atts && (verbose || is_set)) + atts = para_strcat(atts, verbose? "," : " "); + if (is_set || verbose) + atts = para_strcat(atts, row2[0]); + if (verbose) + atts = para_strcat(atts, is_set? "=\"1\"" : "=\"0\""); + } + ret = 1; +out: + if (result2) + mysql_free_result(result2); + if (result) + mysql_free_result(result); + if (!atts && !verbose) + atts = para_strdup("(none)"); + return atts; +} + +/* never returns NULL in verbose mode */ +static char *get_meta(char *name, int verbose) +{ + MYSQL_ROW row; + void *result = NULL; + char *ebn, *q, *ret = NULL; + const char *verbose_fmt = + "select concat('lastplayed: ', " + "(to_days(now()) - to_days(lastplayed))," + "' day(s). numplayed: ', numplayed, " + "', pic: ', pic_id) " + "from data where name = '%s'"; + /* is that really needed? */ + const char *fmt = "select concat('lastplayed=\\'', lastplayed, " + "'\\', numplayed=\\'', numplayed, " + "'\\', pic_id=\\'', pic_id, '\\'') " + "from data where name = '%s'"; + + if (!(ebn = escaped_basename(name))) + goto out; + q = make_message(verbose? verbose_fmt : fmt, ebn); + free(ebn); + result = get_result(q); + free(q); + if (!result) + goto out; + row = mysql_fetch_row(result); + if (!row || !row[0]) + goto out; + ret = para_strdup(row[0]); +out: + if (result) + mysql_free_result(result); + if (!ret && verbose) + ret = para_strdup("(not yet played)"); + return ret; +} + +static char *get_dir(char *name) +{ + char *ret = NULL, *q, *ebn; + void *result; + MYSQL_ROW row; + + if (!(ebn = escaped_basename(name))) + return NULL; + q = make_message("select dir from dir where name = '%s'", ebn); + free(ebn); + result = get_result(q); + free(q); + if (!result) + return NULL; + row = mysql_fetch_row(result); + if (row && row[0]) + ret = para_strdup(row[0]); + mysql_free_result(result); + return ret; +} + +/* never returns NULL */ +static char *get_current_stream(void) +{ + char *ret; + MYSQL_ROW row; + void *result = get_result("select def from streams where " + "name = 'current_stream'"); + + if (!result) + goto err_out; + row = mysql_fetch_row(result); + if (!row || !row[0]) + goto err_out; + ret = para_strdup(row[0]); + mysql_free_result(result); + return ret; +err_out: + if (result) + mysql_free_result(result); + return para_strdup("(none)"); +} +/* + * Read stream definition of stream streamname and construct mysql + * query. Return NULL on errors. If streamname is NULL, use current + * stream. If that is also NULL, use query that selects everything. + * If filename is NULL, query will list everything, otherwise only + * the score of given file. + */ +static char *get_query(char *streamname, char *filename, int with_path) +{ + char *accept_opts = NULL, *deny_opts = NULL, *score = NULL; + char *where_clause, *order, *query; + char command[255] = ""; /* buffer for sscanf */ + void *result; + MYSQL_ROW row; + char *end, *tmp; + char *select_clause = NULL; + if (!streamname) + tmp = get_current_stream(); + else + tmp = para_strdup(streamname); + if (!strcmp(tmp, "(none)")) { + free(tmp); + if (filename) { + char *ret, *ebn = escaped_basename(filename); + ret = make_message("select to_days(now()) - " + "to_days(lastplayed) from data " + "where name = '%s'", ebn); + free(ebn); + return ret; + } + if (with_path) + return make_message( + "select concat(dir.dir, '/', dir.name) " + "from data, dir where dir.name = data.name " + "order by data.lastplayed" + ); + return make_message( + "select name from data where name is not NULL " + "order by lastplayed" + ); + } + free(tmp); + query = make_message("select def from streams where name = '%s'", + streamname); + result = get_result(query); + free(query); + query = NULL; + if (!result) + goto out; + row = mysql_fetch_row(result); + if (!row || !row[0]) + goto out; + end = row[0]; + while (*end) { + int n; + char *arg, *line = end; + + if (!(end = strchr(line, '\n'))) + break; + *end = '\0'; + end++; + if (sscanf(line, "%200s%n", command, &n) < 1) + continue; + arg = line + n; + if (!strcmp(command, "accept:")) { + char *tmp2 = s_a_r_list(macro_list, arg); + if (accept_opts) + accept_opts = para_strcat( + accept_opts, " or "); + accept_opts = para_strcat(accept_opts, tmp2); + free(tmp2); + continue; + } + if (!strcmp(command, "deny:")) { + char *tmp2 = s_a_r_list(macro_list, arg); + if (deny_opts) + deny_opts = para_strcat(deny_opts, " or "); + deny_opts = para_strcat(deny_opts, tmp2); + free(tmp2); + continue; + } + if (!strcmp(command, "score:")) + score = s_a_r_list(macro_list, arg); + } + if (!score) { + score = s_a_r_list(macro_list, conf.mysql_default_score_arg); + if (!score) + goto out; + } + if (filename) { + char *ebn = escaped_basename(filename); + if (!ebn) + goto out; + select_clause = make_message("select %s from data ", score); + free(score); + where_clause = make_message( "where name = '%s' ", ebn); + free(ebn); + order = para_strdup(""); + goto write_query; + } + select_clause = para_strdup(with_path? + "select concat(dir.dir, '/', dir.name) from data, dir " + "where dir.name = data.name " + : + "select name from data where name is not NULL"); + order = make_message("order by -(%s)", score); + free(score); + if (accept_opts && deny_opts) { + where_clause = make_message("and ((%s) and not (%s)) ", + accept_opts, deny_opts); + goto write_query; + } + if (accept_opts && !deny_opts) { + where_clause = make_message("and (%s) ", accept_opts); + goto write_query; + } + if (!accept_opts && deny_opts) { + where_clause = make_message("and not (%s) ", deny_opts); + goto write_query; + } + where_clause = para_strdup(""); +write_query: + query = make_message("%s %s %s", select_clause, where_clause, order); + free(order); + free(select_clause); + free(where_clause); +out: + if (accept_opts) + free(accept_opts); + if (deny_opts) + free(deny_opts); + if (result) + mysql_free_result(result); + return query; +} + + + +/* + * This is called from server and from some commands. Name must not be NULL + * Never returns NULL. + */ +static char *get_dbinfo(char *name) +{ + char *meta = NULL, *atts = NULL, *info, *dir = NULL, *query, *stream = NULL; + void *result = NULL; + MYSQL_ROW row = NULL; + + if (!name) + return para_strdup("(none)"); + stream = get_current_stream(); + meta = get_meta(name, 1); + atts = get_atts(name, 0); + dir = get_dir(name); + /* get score */ + query = get_query(stream, name, 0); + if (!query) + goto write; + result = get_result(query); + free(query); + if (result) + row = mysql_fetch_row(result); +write: + info = make_message("dbinfo1:dir: %s\n" + "dbinfo2:stream: %s, %s, score: %s\n" + "dbinfo3:%s\n", + dir? dir : "(not contained in table)", + stream, meta, + (result && row && row[0])? row[0] : "(no score)", + atts); + if (dir) + free(dir); + if (meta) + free(meta); + if (atts) + free(atts); + if (stream) + free(stream); + if (result) + mysql_free_result(result); + return info; +} + + +/* might return NULL */ +static char *get_current_audio_file(void) +{ + char *name; + mmd_lock(); + name = para_basename(mmd->filename); + mmd_unlock(); + return name; +} + + +/* print database info */ +static int com_info(int fd, int argc, char *argv[]) +{ + char *name = NULL, *meta = NULL, *atts = NULL, *dir = NULL; + int ret, com_la = strcmp(argv[0], "info"); + + if (argc < 1) { + ret = -E_GET_AUDIO_FILE; + if (!(name = get_current_audio_file())) + goto out; + ret = send_va_buffer(fd, "%s\n", name); + if (ret < 0) + goto out; + } else { + ret = -E_ESCAPE; + if (!(name = escaped_basename(argv[1]))) + goto out; + } + meta = get_meta(name, 1); + atts = get_atts(name, 0); + dir = get_dir(name); + if (com_la) + ret = send_va_buffer(fd, "%s\n", atts); + else + ret = send_va_buffer(fd, "dir: %s\n" "%s\n" "attributes: %s\n", + dir? dir : "(not contained in table)", meta, atts); +out: + if (meta) + free(meta); + if (atts) + free(atts); + if (dir) + free(dir); + if (name) + free(name); + return ret; +} +static int change_stream(char *stream) +{ + char *query; + int ret; + /* try to insert if it does not exist (compatibility) */ +// query = make_message("insert into streams (name, def) values " +// "('current_stream', '%s')", stream); +// real_query(query); /* ignore return value */ +// free(query); + query = make_message("update streams set def='%s' " + "where name = 'current_stream'", stream); + ret = real_query(query); + free(query); + return ret; +} + +static int get_pic_id_by_name(char *name) +{ + char *q, *ebn; + void *result = NULL; + long unsigned ret; + MYSQL_ROW row; + + if (!(ebn = escaped_basename(name))) + return -E_ESCAPE; + q = make_message("select pic_id from data where name = '%s'", ebn); + free(ebn); + result = get_result(q); + free(q); + if (!result) + return -E_NORESULT; + row = mysql_fetch_row(result); + ret = -E_NOROW; + if (row && row[0]) + ret = atol(row[0]); + mysql_free_result(result); + return ret; +} + +static int remove_entry(const char *name) +{ + char *q, *ebn = escaped_basename(name); + int ret = -E_ESCAPE; + + if (!ebn || !*ebn) + goto out; + q = make_message("delete from data where name = '%s'", ebn); + real_query(q); /* ignore errors */ + free(q); + q = make_message("delete from dir where name = '%s'", ebn); + real_query(q); /* ignore errors */ + free(q); + ret = 1; +out: + free(ebn); + return ret; +} + +static int add_entry(const char *name) +{ + char *q, *dn, *ebn = NULL, *edn = NULL; + int ret; + + if (!name || !*name) + return -E_MYSQL_SYNTAX; + ebn = escaped_basename(name); + if (!ebn) + return -E_ESCAPE; + ret = -E_MYSQL_SYNTAX; + dn = para_dirname(name); + if (!dn) + goto out; + ret = -E_ESCAPE; + edn = escape_str(dn); + free(dn); + if (!edn || !*edn) + goto out; + q = make_message("insert into data (name, pic_id) values " + "('%s', '%s')", ebn, "1"); + ret = real_query(q); +// ret = 1; PARA_DEBUG_LOG("q: %s\n", q); + free(q); + if (ret < 0) + goto out; + q = make_message("insert into dir (name, dir) values " + "('%s', '%s')", ebn, edn); +// ret = 1; PARA_DEBUG_LOG("q: %s\n", q); + ret = real_query(q); + free(q); +out: + if (ebn) + free(ebn); + if (edn) + free(edn); + return ret; +} + +/* + * remove/add entries + */ +static int com_rm_ne(__unused int fd, int argc, char *argv[]) +{ + int ne = !strcmp(argv[0], "ne"); + int i, ret; + if (argc < 1) + return -E_MYSQL_SYNTAX; + for (i = 1; i <= argc; i++) { + ret = remove_entry(argv[i]); + if (ret < 0) + return ret; + if (!ne) + continue; + ret = add_entry(argv[i]); + if (ret < 0) + return ret; + } + return 1; +} + +/* + * mv: rename entry + */ +static int com_mv(__unused int fd, int argc, char *argv[]) +{ + char *q, *dn, *ebn1 = NULL, *ebn2 = NULL, *edn = NULL; + int ret; + + if (argc != 2) + return -E_MYSQL_SYNTAX; + ebn1 = escaped_basename(argv[1]); + ebn2 = escaped_basename(argv[2]); + dn = para_dirname(argv[2]); + edn = escape_str(dn); + free(dn); + ret = -E_ESCAPE; + if (!ebn1 || !ebn2) + goto out; + remove_entry(ebn2); + q = make_message("update data set name = '%s' where name = '%s'", + ebn2, ebn1); + ret = real_query(q); + free(q); + if (ret < 0) + goto out; + q = make_message("update dir set name = '%s' where name = '%s'", + ebn2, ebn1); + ret = real_query(q); + free(q); + if (ret < 0) + goto out; + /* do not touch table dir, return success if argv[2] is no full path */ + ret = 1; + if (!edn || !*edn) + goto out; + q = make_message("update dir set dir = '%s' where name = '%s'", + edn, ebn2); +// PARA_DEBUG_LOG("q: %s\n", q); + ret = real_query(q); + free(q); +out: + if (ebn1) + free(ebn1); + if (ebn2) + free(ebn2); + if (edn) + free(edn); + return ret; + +} + +/* + * picass: associate pic to audio file + * snp: set numplayed + */ +static int com_set(__unused int fd, int argc, char *argv[]) +{ + char *q, *ebn; + long unsigned id; + int i, ret; + char *field = strcmp(argv[0], "picass")? "numplayed" : "pic_id"; + + if (argc < 2) + return -E_MYSQL_SYNTAX; + id = atol(argv[1]); + for (i = 2; i <= argc; i++) { + ebn = escaped_basename(argv[i]); + if (!ebn) + return -E_ESCAPE; + q = make_message("update data set %s = %lu " + "where name = '%s'", field, id, ebn); + free(ebn); + ret = real_query(q); + free(q); + if (ret < 0) + return ret; + } + return 1; +} + +/* + * picch: change entry's name in pics table + */ +static int com_picch(__unused int fd, int argc, char *argv[]) +{ + int ret; + long unsigned id; + char *q; + + if (argc != 2) + return -E_MYSQL_SYNTAX; + id = atol(argv[1]); + if (strlen(argv[2]) > MAXLINE) + return -E_NAMETOOLONG; + q = make_message("update pics set name = '%s' where id = %lu", argv[2], id); + ret = real_query(q); + free(q); + return ret; +} + +/* + * piclist: print list of pics in db + */ +static int com_piclist(__unused int fd, int argc, __unused char *argv[]) +{ + void *result = NULL; + MYSQL_ROW row; + unsigned long *length; + int ret; + + if (argc) + return -E_MYSQL_SYNTAX; + result = get_result("select id,name,pic from pics order by id"); + if (!result) + return -E_NORESULT; + while ((row = mysql_fetch_row(result))) { + length = mysql_fetch_lengths(result); + if (!row || !row[0] || !row[1] || !row[2]) + continue; + ret = send_va_buffer(fd, "%s\t%lu\t%s\n", row[0], length[2], row[1]); + if (ret < 0) + goto out; + } + ret = 1; +out: + mysql_free_result(result); + return ret; +} + +/* + * picdel: delete picture from database + */ +static int com_picdel(int fd, int argc, char *argv[]) +{ + char *q; + long unsigned id; + my_ulonglong aff; + int i, ret; + + if (argc < 1) + return -E_MYSQL_SYNTAX; + for (i = 1; i <= argc; i++) { + id = atol(argv[i]); + q = make_message("delete from pics where id = %lu", id); + ret = real_query(q); + free(q); + if (ret < 0) + return ret; + aff = mysql_affected_rows(mysql_ptr); + if (!aff) { + ret = send_va_buffer(fd, "No such id: %lu\n", id); + if (ret < 0) + return ret; + continue; + } + q = make_message("update data set pic_id = 1 where pic_id = %lu", id); + ret = real_query(q); + free(q); + } + return 1; +} +/* + * pic: get picture by name or by number + */ +static int com_pic(int fd, int argc, char *argv[]) +{ + void *result = NULL; + MYSQL_ROW row; + unsigned long *length, id; + int ret; + char *q, *name = NULL; + + if (argc < 1) { + ret = -E_GET_AUDIO_FILE; + name = get_current_audio_file(); + } else { + ret = -E_ESCAPE; + name = escaped_basename(argv[1]); + } + if (!name) + return ret; + if (*name == '#') + id = atoi(name + 1); + else + id = get_pic_id_by_name(name); + free(name); + if (id <= 0) + return id; + q = make_message("select pic from pics where id = '%lu'", id); + result = get_result(q); + free(q); + if (!result) + return -E_NORESULT; + row = mysql_fetch_row(result); + ret = -E_NOROW; + if (!row || !row[0]) + goto out; + length = mysql_fetch_lengths(result); + ret = send_bin_buffer(fd, row[0], *length); +out: + mysql_free_result(result); + return ret; +} + +/* strdel */ +static int com_strdel(__unused int fd, int argc, char *argv[]) +{ + char *tmp; + int ret = -1; + + if (argc < 1) + return -E_MYSQL_SYNTAX; + tmp = make_message("delete from streams where name='%s'", argv[1]); + ret = real_query(tmp); + free(tmp); + if (ret < 0) + return ret; + tmp = get_current_stream(); + ret = 1; + if (strcmp(tmp, "(none)") && !strcmp(tmp, argv[1])) + ret = change_stream("(none)"); + return ret; +} + +/* + * ls + */ +static int com_ls(int fd, int argc, char *argv[]) +{ + char *q; + void *result; + int ret; + unsigned int num_rows; + + if (argc > 0) + q = make_message("select name from data where name LIKE '%s'", + argv[1]); + else + q = para_strdup("select name from data"); + result = get_result(q); + free(q); + if (!result) + return -E_NORESULT; + num_rows = mysql_num_rows(result); + ret = 1; + if (num_rows) + ret = print_results(fd, result, 0, 0, num_rows - 1, 0); + mysql_free_result(result); + return ret; +} +/* + * summary + */ +static int com_summary(__unused int fd, int argc, __unused char *argv[]) +{ + MYSQL_ROW row; + MYSQL_ROW row2; + void *result; + void *result2 = NULL; + const char *fmt = "select count(name) from data where %s='1'"; + int ret = -E_NORESULT; + + if (argc) + return -E_MYSQL_SYNTAX; + result = get_all_attributes(); + if (!result) + goto out; + while ((row = mysql_fetch_row(result))) { + char *buf; + + ret = -E_NOROW; + if (!row[0]) + goto out; + ret = -E_NORESULT; + buf = make_message(fmt, row[0]); + result2 = get_result(buf); + free(buf); + if (!result2) + goto out; + ret = -E_NOROW; + row2 = mysql_fetch_row(result2); + if (!row2 || !row2[0]) + goto out; + ret = send_va_buffer(fd, "%s\t%s\n", row[0], row2[0]); + if (ret < 0) + goto out; + } + ret = 1; +out: + if (result2) + mysql_free_result(result2); + if (result) + mysql_free_result(result); + return ret; +} + +static int get_numplayed(char *name) +{ + void *result; + MYSQL_ROW row; + const char *fmt = "select numplayed from data where name = '%s'"; + char *buf = make_message(fmt, name); + int ret = -E_NORESULT; + + result = get_result(buf); + free(buf); + if (!result) + goto out; + ret = -E_NOROW; + row = mysql_fetch_row(result); + if (!row || !row[0]) + goto out; + ret = atoi(row[0]); +out: + if (result) + mysql_free_result(result); + return ret; +} + +static int update_audio_file(char *name) +{ + int ret; + const char *fmt1 = "update data set lastplayed = now() where name = '%s'"; + const char *fmt2 = "update data set numplayed = %i where name = '%s'"; + char *q; + char *ebn = escaped_basename(name); + + ret = -E_ESCAPE; + if (!ebn) + goto out; + q = make_message(fmt1, ebn); + ret = real_query(q); + free(q); + if (ret < 0) + goto out; + ret = get_numplayed(ebn); + if (ret < 0) + goto out; + q = make_message(fmt2, ret + 1, ebn); + ret = real_query(q); + free(q); +out: + if (ebn) + free(ebn); + return ret; +} +/* If called as child, mmd_lock must be held */ +static void update_mmd(char *info) +{ + PARA_DEBUG_LOG("%s", "updating shared memory area\n"); + strncpy(mmd->dbinfo, info, MMD_INFO_SIZE - 1); + mmd->dbinfo[MMD_INFO_SIZE - 1] = '\0'; +} + +static void update_audio_file_server_handler(char *name) +{ + char *info; + info = get_dbinfo(name); + update_mmd(info); + free(info); + update_audio_file(name); +} + +static int com_us(__unused int fd, int argc, char *argv[]) +{ + if (argc != 1) + return -E_MYSQL_SYNTAX; + return update_audio_file(argv[1]); +} + +static void refresh_mmd_dbinfo(void) +{ + char *name = get_current_audio_file(); + char *info; + + if (!name) + return; + info = get_dbinfo(name); + free(name); + mmd_lock(); + update_mmd(info); + mmd_unlock(); + free(info); +} + +/* select previous/next stream */ +static int com_ps(__unused int fd, int argc, char *argv[]) +{ + char *query, *stream = get_current_stream(); + void *result = get_result("select name from streams"); + MYSQL_ROW row; + int match = -1, ret, i; + unsigned int num_rows; + + if (argc) + return -E_MYSQL_SYNTAX; + ret = -E_NORESULT; + if (!result) + goto out; + num_rows = mysql_num_rows(result); + ret = -E_EMPTY_RESULT; + if (num_rows < 2) + goto out; + ret = -E_NOROW; + for (i = 0; i < num_rows; i++) { + row = mysql_fetch_row(result); + if (!row || !row[0]) + goto out; + if (!strcmp(row[0], "current_stream")) + continue; + if (!strcmp(row[0], stream)) { + match = i; + break; + } + } + ret = -E_NO_STREAM; + if (match < 0) + goto out; + if (!strcmp(argv[0], "ps")) + i = match > 0? match - 1 : num_rows - 1; + else + i = match < num_rows - 1? match + 1 : 0; + ret = -E_NOROW; + mysql_data_seek(result, i); + row = mysql_fetch_row(result); + if (!row || !row[0]) + goto out; + if (!strcmp(row[0], "current_stream")) { + if (!strcmp(argv[0], "ps")) { + i = match - 2; + i = i < 0? i + num_rows : i; + } else { + i = match + 2; + i = i > num_rows - 1? i - num_rows : i; + } + mysql_data_seek(result, i); + row = mysql_fetch_row(result); + if (!row || !row[0]) + goto out; + } + query = make_message("update streams set def='%s' where name = " + "'current_stream'", row[0]); + ret = real_query(query); + free(query); + refresh_mmd_dbinfo(); +out: + free(stream); + if (result) + mysql_free_result(result); + return ret; +} + +/* streams */ +static int com_streams(int fd, int argc, __unused char *argv[]) +{ + unsigned int num_rows; + int i, ret = -E_NORESULT; + void *result; + MYSQL_ROW row; + + if (argc && strcmp(argv[1], "current_stream")) + return -E_MYSQL_SYNTAX; + if (argc) { + char *cs = get_current_stream(); + ret = send_va_buffer(fd, "%s\n", cs); + free(cs); + return ret; + } + result = get_result("select name from streams"); + if (!result) + goto out; + num_rows = mysql_num_rows(result); + ret = 1; + if (!num_rows) + goto out; + ret = -E_NOROW; + for (i = 0; i < num_rows; i++) { + row = mysql_fetch_row(result); + if (!row || !row[0]) + goto out; + if (strcmp(row[0], "current_stream")) + send_va_buffer(fd, "%s\n", row[0]); + } + ret = 1; +out: + if (result) + mysql_free_result(result); + return ret; +} + +/* query stream definition */ +static int com_strq(int fd, int argc, char *argv[]) +{ + MYSQL_ROW row; + char *query, *name; + void *result; + int ret; + + if (argc < 1) { + ret = -E_GET_STREAM; + name = get_current_stream(); + } else { + ret = -E_ESCAPE; + name = escaped_basename(argv[1]); + } + if (!name) + return ret; + ret = -E_NORESULT; + query = make_message("select def from streams where name='%s'", name); + free(name); + result = get_result(query); + free(query); + if (!result) + goto out; + ret = -E_NOROW; + row = mysql_fetch_row(result); + if (!row || !row[0]) + goto out; + /* no '\n' needed */ + ret = send_buffer(fd, row[0]); +out: + if (result) + mysql_free_result(result); + return ret; +} + +/* change stream / change stream and play */ +static int com_cs(int fd, int argc, char *argv[]) +{ + int ret, stream_change; + char *query; + char *old_stream = get_current_stream(); + int csp = !strcmp(argv[0], "csp"); + + if (!argc) { + ret = -E_MYSQL_SYNTAX; + if (csp) + goto out; + ret = send_va_buffer(fd, "%s\n", old_stream); + goto out; + } + ret = -E_GET_QUERY; + query = get_query(argv[1], NULL, 0); /* test if stream is valid */ + if (!query) + goto out; + free(query); + /* stream is ok */ + stream_change = strcmp(argv[1], old_stream); + if (stream_change) { + ret = change_stream(argv[1]); + if (ret < 0) + goto out; + refresh_mmd_dbinfo(); + } + if (csp) { + mmd_lock(); + mmd->new_afs_status_flags |= AFS_PLAYING; + if (stream_change) + mmd->new_afs_status_flags |= AFS_NEXT; + mmd_unlock(); + } + ret = 1; +out: + free(old_stream); + return ret; +} + +/* + * sl/skip + */ +static int com_sl(int fd, int argc, char *argv[]) +{ + void *result = NULL; + MYSQL_ROW row; + int ret, i, skip = !strcmp(argv[0], "skip"); + char *query, *stream, *tmp; + unsigned int num_rows, num; + + if (argc < 1) + return -E_MYSQL_SYNTAX; + num = atoi(argv[1]); + if (!num) + return -E_MYSQL_SYNTAX; + stream = (argc == 1)? get_current_stream() : para_strdup(argv[2]); + tmp = get_query(stream, NULL, 0); + query = make_message("%s limit %d", tmp, num); + free(tmp); + ret = -E_GET_QUERY; + free(stream); + if (!query) + goto out; + ret = -E_NORESULT; + result = get_result(query); + free(query); + if (!result) + goto out; + ret = -E_EMPTY_RESULT; + num_rows = mysql_num_rows(result); + if (!num_rows) + goto out; + for (i = 0; i < num_rows && i < num; i++) { + row = mysql_fetch_row(result); + if (skip) { + send_va_buffer(fd, "Skipping %s\n", row[0]); + update_audio_file(row[0]); + } else + send_va_buffer(fd, "%s\n", row[0]? row[0]: "BUG"); + } + ret = 1; +out: + if (result) + mysql_free_result(result); + return ret; +} + +/* + * update attributes of name + */ +static int update_atts(int fd, char *name, char *atts) +{ + int ret; + char *ebn, *q, *old, *new = NULL; + + if (!mysql_ptr) + return -E_NOTCONN; + ebn = escaped_basename(name); + if (!ebn) + return -E_ESCAPE; + q = make_message("update data set %s where name = '%s'", atts, ebn); + old = get_atts(ebn, 0); + send_va_buffer(fd, "old: %s\n", old); + free(old); + ret = real_query(q); + free(q); + if (ret < 0) + goto out; + new = get_atts(ebn, 0); + ret = send_va_buffer(fd, "new: %s\n", new); + free(new); +out: + free(ebn); + return ret; +} + +/* + * set attributes + */ +static int com_sa(int fd, int argc, char *argv[]) +{ + int i, ret; + char *atts = NULL, *name; + + if (argc < 1) + return -E_MYSQL_SYNTAX; + for (i = 1; i <= argc; i++) { + int unset = 0; + char *tmp, *p =argv[i]; + int len = strlen(p); + + if (!len) + continue; + switch (p[len - 1]) { + case '+': + unset = 0; + break; + case '-': + unset = 1; + break; + default: + goto no_more_atts; + } + p[len - 1] = '\0'; + tmp = make_message("%s%s='%s'", atts? "," : "", p, + unset? "0" : "1"); + atts = para_strcat(atts, tmp); + free(tmp); + } +no_more_atts: + if (!atts) + return -E_NOATTS; + if (i > argc) { /* no name given, use current af */ + ret = -E_GET_AUDIO_FILE; + if (!(name = get_current_audio_file())) + goto out; + ret = update_atts(fd, name, atts); + free(name); + } else { + ret = 1; + for (; argv[i] && ret >= 0; i++) + ret = update_atts(fd, argv[i], atts); + } + refresh_mmd_dbinfo(); +out: + return ret; +} + +/* + * copy attributes + */ +static int com_cam(int fd, int argc, char *argv[]) +{ + char *name = NULL, *meta = NULL, *atts = NULL; + int i, ret; + + if (argc < 2) + return -E_MYSQL_SYNTAX; + if (!(name = escaped_basename(argv[1]))) + return -E_ESCAPE; + ret = -E_NOATTS; + if (!(atts = get_atts(name, 1))) + goto out; + ret = -E_META; + if (!(meta = get_meta(name, 0))) + goto out; + for (i = 2; i <= argc; i++) { + char *ebn, *q; + ret = -E_ESCAPE; + if (!(ebn = escaped_basename(argv[i]))) + goto out; + ret = send_va_buffer(fd, "updating %s\n", ebn); + if (ret < 0) { + free(ebn); + goto out; + } + q = make_message("update data set %s where name = '%s'", + meta, ebn); + if ((ret = update_atts(fd, ebn, atts)) >= 0) + ret = real_query(q); + free(ebn); + free(q); + if (ret < 0) + goto out; + } + ret = 1; +out: + if (name) + free(name); + if (meta) + free(meta); + if (atts) + free(atts); + return ret; +} + +/* + * verify / clean + */ +static int com_vrfy(int fd, int argc, __unused char *argv[]) +{ + char *query; + int ret, vrfy_mode = strcmp(argv[0], "clean"); + void *result = NULL; + unsigned int num_rows; + MYSQL_ROW row; + char *escaped_name; + + if (argc) + return -E_MYSQL_SYNTAX; + ret = -E_NORESULT; + result = get_result("select data.name from data left join dir on " + "dir.name = data.name where dir.name is NULL"); + if (!result) + goto out; + num_rows = mysql_num_rows(result); + if (!num_rows) { + ret = send_buffer(fd, "No invalid entries\n"); + goto out; + } + if (vrfy_mode) { + send_va_buffer(fd, "found %i invalid entr%s\n", num_rows, + num_rows == 1? "y" : "ies"); + ret = print_results(fd, result, 0, 0, num_rows - 1, 0); + goto out; + } + while ((row = mysql_fetch_row(result))) { + ret = -E_NOROW; + if (!row[0]) + goto out; + ret = -E_ESCAPE; + escaped_name = escape_str(row[0]); + if (!escaped_name) + goto out; + send_va_buffer(fd, "deleting %s\n", escaped_name); + query = make_message("delete from data where name = '%s'", + escaped_name); + ret = real_query(query); + free(query); + if (ret < 0) + goto out; + } + +out: + if (result) + mysql_free_result(result); + return ret; +} + +static FILE *out_file; + +static int mysql_write_tmp_file(const char *dir, const char *name) +{ + int ret = -E_TMPFILE; + char *msg = make_message("%s\t%s\n", dir, name); + + if (fputs(msg, out_file) != EOF) + ret = 1; + free(msg); + return ret; +} + +/* + * update database + */ +static int com_upd(int fd, int argc, __unused char *argv[]) +{ + char *tempname = NULL, *query = NULL; + int ret, out_fd = -1, num = 0; + void *result = NULL; + unsigned int num_rows; + MYSQL_ROW row; + + if (argc) + return -E_MYSQL_SYNTAX; + out_file = NULL; + tempname = para_strdup("/tmp/mysql.tmp.XXXXXX"); + ret = para_mkstemp(tempname, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (ret < 0) + goto out; + out_fd = ret; + out_file = fdopen(out_fd, "w"); + if (!out_file) { + close(out_fd); + goto out; + } + if (find_audio_files(conf.mysql_audio_file_dir_arg, mysql_write_tmp_file) < 0) + goto out; + num = ftell(out_file); + /* + * we have to make sure the file hit the disk before we call + * real_query + */ + fclose(out_file); + out_file = NULL; + PARA_DEBUG_LOG("wrote tempfile %s (%d bytes)\n", tempname, num); + if (!num) + goto out; + if ((ret = real_query("delete from dir")) < 0) + goto out; + query = make_message("load data infile '%s' into table dir " + "fields terminated by '\t' lines terminated by '\n' " + "(dir, name)", tempname); + ret = real_query(query); + free(query); + if (ret < 0) + goto out; + result = get_result("select dir.name from dir left join data on " + "data.name = dir.name where data.name is NULL"); + ret = -E_NORESULT; + if (!result) + goto out; + num_rows = mysql_num_rows(result); + if (!num_rows) { + ret = send_buffer(fd, "no new entries\n"); + goto out; + } + while ((row = mysql_fetch_row(result))) { + ret = -E_NOROW; + if (!row[0]) + goto out; + send_va_buffer(fd, "new entry: %s\n", row[0]); + query = make_message("insert into data (name, pic_id) values " + "('%s','%s')", row[0], "1"); + ret = real_query(query); + free(query); + if (ret < 0) + goto out; + } + ret = 1; +out: + if (out_fd >= 0) + unlink(tempname); + free(tempname); + if (out_file) + fclose(out_file); + if (result) + mysql_free_result(result); + return ret; +} + +static char **server_get_audio_file_list(unsigned int num) +{ + char **list = para_malloc((num + 1) * sizeof(char *)); + char *tmp, *query, *stream = get_current_stream(); + void *result = NULL; + unsigned int num_rows; + int i = 0; + MYSQL_ROW row; + + tmp = get_query(stream, NULL, 1); + free(stream); + query = make_message("%s limit %d", tmp, num); + free(tmp); + if (!query) + goto err_out; + result = get_result(query); + if (!result) + goto err_out; + num_rows = mysql_num_rows(result); + if (!num_rows) + goto err_out; + for (i = 0; i < num_rows && i < num; i++) { + row = mysql_fetch_row(result); + if (!row || !row[0]) + goto err_out; + list[i] = para_strdup(row[0]); + } + list[i] = NULL; + goto success; +err_out: + while (i > 0) { + i--; + free(list[i]); + } + free(list); + list = NULL; +success: + if (query) + free(query); + if (result) + mysql_free_result(result); + return list; +} + +/* + * connect to mysql server, return mysql pointer on success, -E_NOTCONN + * on errors. Called from parent on startup and also from com_cdb(). + */ +static int init_mysql_server(void) +{ + char *u = conf.mysql_user_arg? conf.mysql_user_arg : para_logname(); + + mysql_ptr = mysql_init(NULL); + if (!mysql_ptr) { + PARA_CRIT_LOG("%s", "mysql init error\n"); + return -E_NOTCONN; + } + PARA_DEBUG_LOG("connecting: %s@%s:%d\n", u, conf.mysql_host_arg, + conf.mysql_port_arg); + if (!conf.mysql_user_arg) + free(u); + /* + * If host is NULL a connection to the local host is assumed, + * If user is NULL, the current user is assumed + */ + if (!(mysql_ptr = mysql_real_connect(mysql_ptr, + conf.mysql_host_arg, + conf.mysql_user_arg, + conf.mysql_passwd_arg, + conf.mysql_database_arg, + conf.mysql_port_arg, NULL, 0))) { + PARA_CRIT_LOG("%s", "connect error\n"); + return -E_NOTCONN; + } + PARA_INFO_LOG("%s", "success\n"); + return 1; +} + +/* mmd lock must be held */ +static void write_msg2mmd(int success) +{ + sprintf(mmd->dbinfo, "dbinfo1:%s\ndbinfo2:mysql-%s\ndbinfo3:\n", + success < 0? PARA_STRERROR(-success) : + "successfully connected to mysql server", + success < 0? "" : mysql_get_server_info(mysql_ptr)); +} + +/* create database */ +static int com_cdb(int fd, int argc, char *argv[]) +{ + char *query, *name; + int ret; + + if (argc < 1) + name = "paraslash"; + else { + ret = -E_NAMETOOLONG; + name = argv[1]; + if (strlen(name) > MAXLINE) + goto out; + } + if (mysql_ptr) { + PARA_INFO_LOG("%s", "closing database\n"); + mysql_close(mysql_ptr); + } + /* dont use any database */ + conf.mysql_database_arg = NULL; /* leak? */ + ret = -E_MYSQL_INIT; + if (init_mysql_server() < 0 || !mysql_ptr) + goto out; + query = make_message("create database %s", name); + ret = real_query(query); + free(query); + if (ret < 0) + goto out; + /* reconnect with database just created */ + mysql_close(mysql_ptr); + conf.mysql_database_arg = para_strdup(name); + ret = -E_MYSQL_INIT; + if (init_mysql_server() < 0 || !mysql_ptr) + goto out; + mmd_lock(); + write_msg2mmd(1); + mmd_unlock(); + ret = -E_QFAILED; + if (real_query("create table data (name varchar(255) binary not null " + "primary key, " + "lastplayed datetime not null default " + "'1970-01-01', " + "numplayed int not null default 0, " + "pic_id bigint unsigned not null default 1)") < 0) + goto out; + if (real_query("create table dir (name varchar(255) binary not null " + "primary key, dir varchar(255) default null)") < 0) + goto out; + if (real_query("create table pics (" + "id bigint(20) unsigned not null primary key " + "auto_increment, " + "name varchar(255) binary not null, " + "pic mediumblob not null)") < 0) + goto out; + if (real_query("create table streams (" + "name varchar(255) binary not null primary key, " + "def blob not null)") < 0) + goto out; + if (real_query("insert into streams (name, def) values " + "('current_stream', '(none)')") < 0) + goto out; + ret = send_va_buffer(fd, "successfully created database %s\n", name); +out: + return ret; +} + +static void shutdown_connection(void) +{ + if (mysql_ptr) { + PARA_NOTICE_LOG("%s", "shutting down mysql connection\n"); + mysql_close(mysql_ptr); + mysql_ptr = NULL; + } +} + +/** + * the init function of the mysql-based database tool + * + * Check the command line options and initialize all function pointers of \a db. + * Connect to the mysql server and initialize the dbinfo string. + * + * \sa struct dbtool, misc_meta_data::dbinfo, dopey.c + */ +int mysql_dbtool_init(struct dbtool *db) +{ + int ret; + + if (!conf.mysql_passwd_given) + return -E_NO_MYSQL_PASSWD; + if (!conf.mysql_audio_file_dir_given) + return -E_NO_AF_DIR; + db->name = "mysql"; + db->cmd_list = cmds; + db->get_audio_file_list = server_get_audio_file_list; + db->update_audio_file = update_audio_file_server_handler; + db->shutdown = shutdown_connection; + ret = init_mysql_server(); + if (ret < 0) + PARA_WARNING_LOG("%s\n", PARA_STRERROR(-ret)); + write_msg2mmd(ret); + return 1; /* return success even if connect failed to give the + * user the chance to exec com_cdb + */ +} diff --git a/net.c b/net.c new file mode 100644 index 00000000..59b8616a --- /dev/null +++ b/net.c @@ -0,0 +1,504 @@ +/* + * Copyright (C) 2005-2006 Andre Noll + * + * 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 +#include "error.h" +#include "string.h" + +extern void (*crypt_function_recv)(unsigned long len, const unsigned char *indata, unsigned char *outdata); +extern void (*crypt_function_send)(unsigned long len, const unsigned char *indata, unsigned char *outdata); + +/** + * initialize a struct sockaddr_in + * @param addr A pointer to the struct to be initialized + * @param port The port number to use + * @param he The address to use + * + * If \a he is null (server mode), \a addr->sin_addr is initialized with \p INADDR_ANY. + * Otherwise, the address given by \a he is copied to addr. + */ +void init_sockaddr(struct sockaddr_in *addr, int port, const struct hostent *he) +{ + /* host byte order */ + addr->sin_family = AF_INET; + /* short, network byte order */ + addr->sin_port = htons(port); + if (he) + addr->sin_addr = *((struct in_addr *)he->h_addr); + else + addr->sin_addr.s_addr = INADDR_ANY; + /* zero the rest of the struct */ + memset(&addr->sin_zero, '\0', 8); +} + +/* + * send out a buffer, resend on short writes + * @param fd the file descriptor + * @param buf The buffer to be sent + * @len The length of \a buf + * + * Due to circumstances beyond your control, the kernel might not send all the + * data out in one chunk, and now, my friend, it's up to us to get the data out + * there (Beej's Guide to Network Programming). + */ +static int sendall(int fd, const char *buf, size_t *len) +{ + int total = 0; /* how many bytes we've sent */ + int bytesleft = *len; /* how many we have left to send */ + int n = -1; + + while (total < *len) { + n = send(fd, buf + total, bytesleft, 0); + if (n == -1) + break; + total += n; + bytesleft -= n; + if (total < *len) + PARA_DEBUG_LOG("short write (%d byte(s) left)", + *len - total); + } + *len = total; /* return number actually sent here */ + return n == -1? -E_SEND : 1; /* return 1 on success */ +} + +/** + * encrypt and send buffer + * @param fd: the file descriptor + * @param buf the buffer to be encrypted and sent + * @param len the length of \a buf + * + * Check if encrytion is available. If yes, encrypt the given buffer. Send out + * the buffer, encrypted or not, and try to resend the remaing part in case of + * short writes. + * + * @return Positive on success, \p -E_SEND on errors. + */ +int send_bin_buffer(int fd, const char *buf, size_t len) +{ + int ret; + + if (!len) + PARA_CRIT_LOG("%s", "len == 0\n"); + if (crypt_function_send) { + unsigned char *outbuf = para_malloc(len); + crypt_function_send(len, (unsigned char *)buf, outbuf); + ret = sendall(fd, (char *)outbuf, &len); + free(outbuf); + } else + ret = sendall(fd, buf, &len); + return ret; +} + +/** + * encrypt and send null terminated buffer. + * @param fd the file descriptor + * @param buf the null-terminated buffer to be send + * + * This is equivalent to send_bin_buffer(fd, buf, strlen(buf)). + * + * @return Positive on success, \p -E_SEND on errors. + */ +int send_buffer(int fd, const char *buf) +{ + return send_bin_buffer(fd, buf, strlen(buf)); +} + + +/** + * send and encrypt a buffer given by a format string + * @param fd the file descriptor + * @param fmt a format string + * + * @return Positive on success, \p -E_SEND on errors. + */ +__printf_2_3 int send_va_buffer(int fd, char *fmt, ...) +{ + char *msg; + int ret; + + PARA_VSPRINTF(fmt, msg); + ret = send_buffer(fd, msg); + free(msg); + return ret; +} + +/** + * receive and decrypt. + * + * @param fd the file descriptor + * @param buf the buffer to write the decrypted data to + * @param size the size of @param buf + * + * Receive at most \a size bytes from filedescriptor fd. If encrytion is + * available, decrypt the received buffer. + * + * @return the number of bytes received on success. On receive errors, -E_RECV + * is returned. On crypt errors, the corresponding crypt error number is + * returned. + * \sa recv(2) + */ +__must_check int recv_bin_buffer(int fd, char *buf, ssize_t size) +{ + int n; + + if (crypt_function_recv) { + unsigned char *tmp = para_malloc(size); + n = recv(fd, tmp, size, 0); + if (n > 0) + crypt_function_recv(n, tmp, (unsigned char *)buf); + free(tmp); + } else + n = recv(fd, buf, size, 0); + if (n == -1) + n = -E_RECV; + return n; +} + +/** + * receive, decrypt and write terminating NULL byte + * + * @param fd the file descriptor + * @param buf the buffer to write the decrypted data to + * @param size the size of \a buf + * + * Read and decrypt at most size - 1 bytes from file descriptor \a fd and write + * a NULL byte at the end of the received data. + * +*/ +int recv_buffer(int fd, char *buf, ssize_t size) +{ + int n; + + if ((n = recv_bin_buffer(fd, buf, size - 1)) >= 0) + buf[n] = '\0'; + return n; +} + +/** + * wrapper around gethostbyname + * + * @param host hostname or IPv4 address + * \return The hostent structure or a NULL pointer if an error occurs + * \sa gethostbyname(2) + */ +struct hostent *get_host_info(char *host) +{ + PARA_INFO_LOG("getting host info of %s\n", host); + /* FIXME: gethostbyname() is obsolete */ + return gethostbyname(host); +} + +/** + * a wrapper around socket(2) + * + * Create an IPv4 socket for sequenced, reliable, two-way, connection-based + * byte streams. + * + * @return The socket fd on success, -E_SOCKET on errors. + * \sa socket(2) + */ +int get_socket(void) +{ + int socket_fd; + + if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) + return -E_SOCKET; + return socket_fd; +} + +/** + * a wrapper around connect(2) + * + * @param fd the file descriptor + * @param their_addr the address to connect + * + * @return \p -E_CONNECT on errors, 1 on success + * \sa connect(2) + */ +int para_connect(int fd, struct sockaddr_in *their_addr) +{ + int ret; + + if ((ret = connect(fd, (struct sockaddr *)their_addr, + sizeof(struct sockaddr))) == -1) + return -E_CONNECT; + return 1; +} + +/** + * paraslash's wrapper around the accept system call + * + * @param fd the listening socket + * @param addr structure which is filled in with the address of the peer socket + * @param size should contain the size of the structure pointed to by \a addr + * + * \sa accept(2). + */ +int para_accept(int fd, void *addr, size_t size) +{ + int new_fd; + + new_fd = accept(fd, (struct sockaddr *) addr, &size); + return new_fd == -1? -E_ACCEPT : new_fd; +} + +static int setserversockopts(int socket_fd) +{ + int yes = 1; + + if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &yes, + sizeof(int)) == -1) + return -E_SETSOCKOPT; + return 1; +} + +static int do_bind(int socket_fd, struct sockaddr_in *my_addr) +{ + if (bind(socket_fd, (struct sockaddr *)my_addr, + sizeof(struct sockaddr)) == -1) + return -E_BIND; + return 1; +} + +/** + * prepare a structure for \p AF_UNIX socket addresses + * + * \param u pointer to the struct to be prepared + * \param name the socket pathname + * + * This just copies \a name to the sun_path component of \a u. + * + * \return Positive on success, \p -E_NAME_TOO_LONG if \a name is longer + * than \p UNIX_PATH_MAX. + */ +int init_unix_addr(struct sockaddr_un *u, const char *name) +{ + if (strlen(name) >= UNIX_PATH_MAX) + return -E_NAME_TOO_LONG; + memset(u->sun_path, 0, UNIX_PATH_MAX); + u->sun_family = PF_UNIX; + strcpy(u->sun_path, name); + return 1; +} + +/** + * prepare, create, and bind and socket for local communication + * + * \param name the socket pathname + * \param unix_addr pointer to the \p AF_UNIX socket structure + * \param mode the desired mode of the socket + * + * This functions creates a local socket for sequenced, reliable, + * two-way, connection-based byte streams. + * \sa socket(2) + * \sa bind(2) + * \sa chmod(2) + */ +int create_pf_socket(const char *name, struct sockaddr_un *unix_addr, int mode) +{ + int fd, ret; + + fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (fd < 0) + return -E_SOCKET; +// unlink(name); + ret = init_unix_addr(unix_addr, name); + if (ret < 0) + return ret; + if (bind(fd, (struct sockaddr *) unix_addr, UNIX_PATH_MAX) < 0) + return -E_BIND; + if (chmod(name, mode) < 0) + return -E_CHMOD; + return fd; +} + +/** + * send NULL terminated buffer and Unix credentials of the current process + * + * \param sock the socket file descriptor + * \param buf the buffer to be sent + * + * \return On success, this call returns the number of characters sent. On + * error, \p -E_SENDMSG ist returned. + * \sa okir's Black Hats Manual + * \sa sendmsg(2) + */ +ssize_t send_cred_buffer(int sock, char *buf) +{ + char control[sizeof(struct cmsghdr) + 10]; + struct msghdr msg; + struct cmsghdr *cmsg; + static struct iovec iov; + struct ucred c; + int ret; + + /* Response data */ + iov.iov_base = buf; + iov.iov_len = strlen(buf); + c.pid = getpid(); + c.uid = getuid(); + c.gid = getgid(); + /* compose the message */ + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control; + msg.msg_controllen = sizeof(control); + /* attach the ucred struct */ + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_CREDENTIALS; + cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred)); + *(struct ucred *)CMSG_DATA(cmsg) = c; + msg.msg_controllen = cmsg->cmsg_len; + ret = sendmsg(sock, &msg, 0); + if (ret < 0) + ret = -E_SENDMSG; + return ret; +} + +static void dispose_fds(int *fds, int num) +{ + int i; + + for (i = 0; i < num; i++) + close(fds[i]); +} + +/** + * receive a buffer and the Unix credentials of the sending process + * + * \param fd the socket file descriptor + * \param buf the buffer to store the message + * \param size the size of \a buffer + * \param cred the credentials are returned here + * + * \sa okir's Black Hats Manual + * \sa recvmsg(2) + */ +int recv_cred_buffer(int fd, char *buf, size_t size, struct ucred *cred) +{ + char control[255]; + struct msghdr msg; + struct cmsghdr *cmsg; + struct iovec iov; + int result; + int yes = 1; + + setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &yes, sizeof(int)); + memset(&msg, 0, sizeof(msg)); + memset(buf, 0, size); + iov.iov_base = buf; + iov.iov_len = size; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control; + msg.msg_controllen = sizeof(control); + if (recvmsg(fd, &msg, 0) < 0) + return -E_RECVMSG; + result = -SCM_CREDENTIALS; + cmsg = CMSG_FIRSTHDR(&msg); + while (cmsg) { + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type + == SCM_CREDENTIALS) { + memcpy(cred, CMSG_DATA(cmsg), sizeof(struct ucred)); + result = iov.iov_len; + } else + if (cmsg->cmsg_level == SOL_SOCKET + && cmsg->cmsg_type == SCM_RIGHTS) { + dispose_fds((int *) CMSG_DATA(cmsg), + (cmsg->cmsg_len - CMSG_LEN(0)) + / sizeof(int)); + } + cmsg = CMSG_NXTHDR(&msg, cmsg); + } + return result; +} + +/** how many pending connections queue will hold */ +#define BACKLOG 10 + +/** + * create a socket, bind it and listen + * \param port the tcp port to listen on + * + * \return The file descriptor of the created socket, negative + * on errors. + * + * \sa get_socket() + * \sa setsockopt(2) + * \sa bind(2) + * \sa listen(2) + */ +int init_tcp_socket(int port) +{ + int sockfd, ret; + struct sockaddr_in my_addr; + + if ((sockfd = get_socket()) < 0) + return sockfd; + ret = setserversockopts(sockfd); + if (ret < 0) + return ret; + init_sockaddr(&my_addr, port, NULL); + ret = do_bind(sockfd, &my_addr); + if (ret < 0) + return ret; + if (listen(sockfd, BACKLOG) == -1) + return -E_LISTEN; + PARA_INFO_LOG("listening on port %d, fd %d\n", port, sockfd); + return sockfd; +} + +/** + * receive a buffer and check for a pattern + * + * \param fd the file descriptor to receive from + * \param pattern the expected pattern + * \param bufsize the size of the internal buffer + * + * \return Positive if \a pattern was received, negative otherwise. + * + * This function creates a buffer of size \a bufsize and tries + * to receive at most \a bufsize bytes from file descriptor \a fd. + * If at least \p strlen(\a pattern) bytes were received, the beginning of + * the received buffer is compared with \a pattern, ignoring case. + * \sa recv_buffer() + * \sa strncasecmp(3) + */ +int recv_pattern(int fd, const char *pattern, size_t bufsize) +{ + size_t len = strlen(pattern); + char *buf = para_malloc(bufsize + 1); + int ret = -E_RECV_PATTERN, n = recv_buffer(fd, buf, bufsize); + + if (n < len) + goto out; + buf[n] = '\0'; + if (strncasecmp(buf, pattern, len)) + goto out; + ret = 1; +out: + free(buf); + return ret; +} diff --git a/net.h b/net.h new file mode 100644 index 00000000..ebb3983a --- /dev/null +++ b/net.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2006 Andre Noll + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ + +/** \file net.h exported symbols from net.c */ + +/** + * the buffer size of the sun_path component of struct sockaddr_un + * + * While glibc doesn't define \p UNIX_PATH_MAX, it + * documents it has being limited to 108 bytes. + */ +#ifndef UNIX_PATH_MAX +#define UNIX_PATH_MAX 108 +#endif + +struct hostent *get_host_info(char *); +int get_socket(void); +void init_sockaddr(struct sockaddr_in*, int, const struct hostent*); +int para_connect(int, struct sockaddr_in *); +int send_buffer(int, const char *); +int send_bin_buffer(int, const char *, size_t); +int send_va_buffer(int, char *, ...); +int recv_buffer(int, char *, ssize_t); +int recv_bin_buffer(int, char *, ssize_t); +int para_accept(int, void *addr, size_t size); +int create_pf_socket(const char *, struct sockaddr_un *, int mod); +int init_unix_addr(struct sockaddr_un *, const char *); +int recv_cred_buffer(int, char *, size_t, struct ucred *); +ssize_t send_cred_buffer(int, char*); +int recv_pattern(int fd, const char *pattern, size_t bufsize); +int init_tcp_socket(int port); + diff --git a/ogg.c b/ogg.c new file mode 100644 index 00000000..d3ebd465 --- /dev/null +++ b/ogg.c @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2004-2006 Andre Noll + * + * 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 +#include +#include + +#include "server.cmdline.h" +#include "server.h" +#include "afs.h" +#include "error.h" +#include "string.h" + +/* must be big enough to hold header */ +#define CHUNK_SIZE 32768 +static double chunk_time = 0.25; + +static OggVorbis_File *oggvorbis_file; +static FILE *infile; +static int header_len, oggbuf_len, vi_channels; +static char *header, *oggbuf; +static ogg_int64_t *chunk_table, max_chunk_len; +struct audio_format *af; +static long vi_sampling_rate, vi_bitrate, vi_bitrate_nominal, + num_chunks; + +static int ogg_compute_header_len(void) +{ + int ret, len, in = fileno(infile); + unsigned int serial; + char *buf; + ogg_page page; + ogg_packet packet; + vorbis_comment vc; + vorbis_info vi; + ogg_stream_state *stream_in = para_malloc(sizeof(ogg_stream_state)); + ogg_stream_state *stream_out = para_malloc(sizeof(ogg_stream_state)); + ogg_sync_state *sync_in = para_malloc(sizeof(ogg_sync_state)); + + ogg_sync_init(sync_in); + vorbis_info_init(&vi); + vorbis_comment_init(&vc); + buf = ogg_sync_buffer(sync_in, CHUNK_SIZE); + len = read(in, buf, CHUNK_SIZE); + ret = -E_OGG_READ; + if (len <= 0) + goto err1; + ogg_sync_wrote(sync_in, len); + ret = -E_SYNC_PAGEOUT; + if (ogg_sync_pageout(sync_in, &page) <= 0) + goto err1; + serial = ogg_page_serialno(&page); + ogg_stream_init(stream_in, serial); + ogg_stream_init(stream_out, serial); + ret = ogg_stream_pagein(stream_in, &page); + if (ret < 0) { + ret = E_STREAM_PAGEIN; + goto err2; + } + ret = ogg_stream_packetout(stream_in, &packet); + if (ret != 1) { + ret = -E_STREAM_PACKETOUT; + goto err2; + } + ret = -E_VORBIS; + if (vorbis_synthesis_headerin(&vi, &vc, &packet) < 0) { + goto err2; + } else + PARA_INFO_LOG("channels: %i, rate: %li\n", vi.channels, + vi.rate); + ogg_stream_packetin(stream_out, &packet); + + ret = ogg_sync_pageout(sync_in, &page); + if (ret <= 0) { + ret = -E_SYNC_PAGEOUT; + goto err2; + } + ogg_stream_pagein(stream_in, &page); + ogg_stream_packetout(stream_in, &packet); + ogg_stream_packetin(stream_out, &packet); + + ret = ogg_sync_pageout(sync_in, &page); + if (ret <= 0) { + ret = -E_SYNC_PAGEOUT; + goto err2; + } + ogg_stream_pagein(stream_in, &page); + ogg_stream_packetout(stream_in, &packet); + ogg_stream_packetin(stream_out, &packet); + + header_len = 0; + while (ogg_stream_flush(stream_out, &page)) + header_len += page.body_len + page.header_len; + ret = len; + PARA_INFO_LOG("header_len = %d\n", header_len); +err2: + ogg_stream_destroy(stream_in); + ogg_stream_destroy(stream_out); +err1: + ogg_sync_destroy(sync_in); + vorbis_info_clear(&vi); + vorbis_comment_clear(&vc); + return ret; +} + +static void tunetable(void) +{ + int i = 1, j = -1, lp = 1; + while (i < num_chunks) { + if (chunk_table[i] == chunk_table[lp]) { + i++; + continue; + } + if (j < 0) + tv_scale(i + 2, &af->chunk_tv, &af->eof_tv); + for (j = lp; j < i; j++) + chunk_table[j] = chunk_table[i]; + lp = i; + } +#if 1 + for (i = 2; i < num_chunks; i++) + if (chunk_table[i] != chunk_table[1]) + break; + lp = i; + for (i = 2; i < num_chunks - lp; i++) + chunk_table[i] = chunk_table[i + lp]; +#endif +} + + +/* + * Alloc and fill array table of byte offsets. chunk_table[i] is the + * offset in the current infile at which the sample containing time i * + * CHUNK_TIME begins. + */ +static void ogg_compute_chunk_table(double time_total) +{ + int i, ret, num; + ogg_int64_t pos = 0, min = 0, old_pos; + + old_pos = 0; + ret = 0; + num = time_total / chunk_time + 3; + PARA_DEBUG_LOG("chunk time: %g allocating %d chunk pointers\n", + chunk_time, num); + chunk_table = para_malloc(num * sizeof(ogg_int64_t)); + chunk_table[0] = 0; + max_chunk_len = 0; + rewind(infile); + for (i = 1; ret == 0; i++) { + ogg_int64_t diff; + ret = ov_time_seek(oggvorbis_file, i * chunk_time); + if (ret) + break; + pos = ov_raw_tell(oggvorbis_file); + diff = pos - old_pos; + max_chunk_len = MAX(max_chunk_len, diff); + min = (i == 1)? diff : MIN(min, diff); + chunk_table[i] = pos; + if (i < 11 || !((i - 1) % 1000)|| i > num - 11) + PARA_DEBUG_LOG("chunk #%d: %g secs, pos: %lli, " + "size: %lli\n", i - 1, + i * chunk_time, pos, pos - old_pos); + old_pos = pos; + } + num_chunks = i - 1; + chunk_table[i] = pos; + tunetable(); + PARA_INFO_LOG("%li chunks (%fs), max chunk: %lli, min chunk: %lli\n", + num_chunks, chunk_time, max_chunk_len, min); + rewind(infile); +} + +static void ogg_close_audio_file(void) +{ + if (oggvorbis_file) { + PARA_DEBUG_LOG("%s", "ov_clear\n"); + ov_clear(oggvorbis_file); + free(oggvorbis_file); + oggvorbis_file = NULL; + } + free(header); + header = NULL; + header_len = 0; + free(chunk_table); + chunk_table = NULL; + num_chunks = 0; + free(oggbuf); + oggbuf = NULL; + oggbuf_len = 0; +} + +static int ogg_save_header(FILE *file, int header_len) +{ + int ret; + + header = para_malloc(header_len); + rewind(file); + ret = read(fileno(file), header, header_len); + if (ret != header_len) + return -E_OGG_READ; + return 1; +} + +/* + * Init oggvorbis file and write some tech data to given pointers. + */ +static int ogg_get_file_info(FILE *file, char *info_str, long unsigned *frames, + int *seconds) +{ + int ret; + double time_total; + vorbis_info *vi; + ogg_int64_t raw_total; + + infile = file; + if (!file) + return -E_OGG_NO_FILE; + ret = ogg_compute_header_len(); + if (ret < 0) + return ret; + ret = ogg_save_header(file, header_len); + if (ret < 0) + return ret; + rewind(file); + oggvorbis_file = para_malloc(sizeof(OggVorbis_File)); + ret = ov_open(file, oggvorbis_file, NULL, 0); + if (ret < 0) { + free(oggvorbis_file); + free(header); + return -E_OGG_OPEN; + } + ret = -E_OGG_INFO; + vi = ov_info(oggvorbis_file, 0); + if (!vi) + goto err; + time_total = ov_time_total(oggvorbis_file, -1); + raw_total = ov_raw_total(oggvorbis_file, -1); + *seconds = time_total; + vi_sampling_rate = vi->rate; + vi_bitrate = ov_bitrate(oggvorbis_file, 0); + vi_bitrate_nominal = vi->bitrate_nominal; + vi_channels = vi->channels; + ogg_compute_chunk_table(time_total); + *frames = num_chunks; + sprintf(info_str, "audio_file_info1:%lu x %lu, %ldkHz, %d channels, %ldkbps\n" + "audio_file_info2: \n" + "audio_file_info3: \n", + num_chunks, (long unsigned) (chunk_time * 1000 * 1000), + vi_sampling_rate / 1000, vi_channels, vi_bitrate / 1000 + ); + rewind(file); + return 1; +err: + ogg_close_audio_file(); + return ret; +} + +/* + * Simple stream reposition routine + */ +static int ogg_reposition_stream(long unsigned request) +{ + int ret; + long seek; + + seek = chunk_table[request]; + ret = fseek(infile, seek, SEEK_SET); + PARA_DEBUG_LOG("seek to %li returned %d. offset:%li\n", seek, + ret, ftell(infile)); + return ret < 0? -E_OGG_REPOS : 1; +} + +static ogg_int64_t get_chunk_size(long unsigned chunk_num) +{ + ogg_int64_t ret; + if (chunk_num >= num_chunks) + return -1; + ret = chunk_table[chunk_num + 1] - chunk_table[chunk_num]; + if (!ret) + return ret; +#if 0 + PARA_DEBUG_LOG("chunk %d: %lli-%lli (%lli bytes)\n", + chunk_num, + chunk_table[chunk_num], + chunk_table[chunk_num + 1] - 1, + ret); +#endif + return ret; +} + +char *ogg_read_chunk(long unsigned current_chunk, ssize_t *len) +{ + int ret; + ogg_int64_t cs = get_chunk_size(current_chunk); + if (!cs) { /* nothing to send for this run */ + *len = 0; + return oggbuf; + } + if (cs < 0) { /* eof */ + *len = 0; + return NULL; + } + *len = cs; + if (!oggbuf || oggbuf_len < *len) { + PARA_INFO_LOG("increasing ogg buffer size (%d -> %u)\n", + oggbuf_len, *len); + oggbuf = para_realloc(oggbuf, *len); + oggbuf_len = *len; + } + ret = read(fileno(infile), oggbuf, *len); + if (!ret) { + *len = 0; + return NULL; + } + if (ret < 0) { + *len = -E_OGG_READ; + return NULL; + } + if (ret != *len) + PARA_WARNING_LOG("short read (%d/%d)\n", ret, *len); + *len = ret; + return oggbuf; +} + +static char *ogg_get_header_info(int *len) +{ + *len = header_len; + return header; +} + +void ogg_init(void *p) +{ + af = p; + af->reposition_stream = ogg_reposition_stream; + af->get_file_info = ogg_get_file_info, + af->read_chunk = ogg_read_chunk; + af->close_audio_file = ogg_close_audio_file; + af->get_header_info = ogg_get_header_info; + af->chunk_tv.tv_sec = 0; + af->chunk_tv.tv_usec = 250 * 1000; + tv_scale(3, &af->chunk_tv, &af->eof_tv); +} diff --git a/oggdec.c b/oggdec.c new file mode 100644 index 00000000..4ef16b08 --- /dev/null +++ b/oggdec.c @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2005-2006 Andre Noll + * + * 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 + +/** \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 fci->eof && !fn->fci->error) { + PARA_INFO_LOG("initial input buffer %d/%d, waiting for more data\n", + len, ib); + return 0; + } + pod->vf = para_malloc(sizeof(struct OggVorbis_File)); + PARA_NOTICE_LOG("input buffer: %d, opening ov callbacks\n", len); + ret = ov_open_callbacks(fn, pod->vf, + NULL, /* no initial buffer */ + 0, /* no initial bytes */ + ovc); /* the ov_open_callbacks */ + if (ret == OV_EREAD) + return -E_OGGDEC_READ; + if (ret == OV_ENOTVORBIS) + return -E_OGGDEC_NOTVORBIS; + if (ret == OV_EVERSION) + return -E_OGGDEC_VERSION; + if (ret == OV_EBADHEADER) + return -E_OGGDEC_BADHEADER; + if (ret < 0) + return -E_OGGDEC_FAULT; + fn->fci->channels = ov_info(pod->vf, 0)->channels; + fn->fci->samplerate = ov_info(pod->vf, 0)->rate; + PARA_NOTICE_LOG("%d channels, %d Hz\n", fn->fci->channels, fn->fci->samplerate); + } +again: + ret = ov_read(pod->vf, fn->buf + fn->loaded, fn->bufsize - fn->loaded, + ENDIAN, BITS / 8, SIGN, NULL); + if (ret == OV_HOLE || !ret) { + return pod->converted; + } + if (ret < 0) + return -E_OGGDEC_BADLINK; + fn->loaded += ret; + if (!*fn->fci->eof && !fn->fci->error && fn->loaded < fn->bufsize) + goto again; + return pod->converted; +} + +static void *oggdec_parse_config(int argc, char **argv) +{ + struct gengetopt_args_info *ret = para_calloc(sizeof(struct gengetopt_args_info)); + if (!oggdec_cmdline_parser(argc, argv, ret)) + return ret; + free(ret); + return NULL; +} + +/** the init function of the ogg vorbis decoder */ +void oggdec_init(struct filter *f) +{ + f->open = ogg_open; + f->close = ogg_close; + f->convert = ogg_convert; + f->print_help = oggdec_cmdline_parser_print_help; + f->parse_config = oggdec_parse_config; +} diff --git a/oggdec_filter.ggo b/oggdec_filter.ggo new file mode 100644 index 00000000..e9946bae --- /dev/null +++ b/oggdec_filter.ggo @@ -0,0 +1,2 @@ +option "bufsize" b "size of output buffer" int typestr="kilobyte" default="128" no +option "initial_buffer" i "size of initial input buffer" int typestr="kilobyte" default="16" no diff --git a/ortp.h b/ortp.h new file mode 100644 index 00000000..0222290b --- /dev/null +++ b/ortp.h @@ -0,0 +1,30 @@ +/** \file ortp.h some macros used by ortp_send.c and ortp_recv.c*/ +enum ortp_audio_packet_type {ORTP_EOF, ORTP_BOF, ORTP_HEADER, ORTP_DATA}; + +#define ORTP_AUDIO_HEADER_LEN 10 + +#define WRITE_PACKET_TYPE(buf, x) (buf)[0] = (unsigned char)((x)&0xff) +#define READ_PACKET_TYPE(buf) (unsigned)(buf)[0] + +#define WRITE_CHUNK_TIME(buf, x) (buf)[1] = (unsigned char)((x)&0xff);\ + (buf)[2] = (unsigned char)(((x)>>8)&0xff);\ + (buf)[3] = (unsigned char)(((x)>>16)&0xff);\ + (buf)[4] = (unsigned char)(((x)>>24)&0xff); +#define READ_CHUNK_TIME(buf) (unsigned char)(buf)[1] + \ + ((unsigned char)(buf)[2] << 8) + \ + ((unsigned char)(buf)[3] << 16) + \ + ((unsigned char)(buf)[4] << 24) + +#define WRITE_CHUNK_TS(buf, x) (buf)[5] = (unsigned char)((x) & 0xff); \ + (buf)[6] = (unsigned char)(((x >> 8) & 0xff)); +#define READ_CHUNK_TS(buf) (unsigned char)(buf)[5] + \ + ((unsigned char)(buf)[6] << 8) + +#define WRITE_STREAM_TYPE(buf, x) (buf)[7] = (unsigned char)((x)&0xff) +#define READ_STREAM_TYPE(buf) (unsigned)(buf)[7] + +#define WRITE_HEADER_LEN(buf, x) (buf)[8] = (unsigned char)((x) & 0xff); \ + (buf)[9] = (unsigned char)(((x >> 8) & 0xff)); +#define READ_HEADER_LEN(buf) (unsigned char)(buf)[8] + \ + ((unsigned char)(buf)[9] << 8) + diff --git a/ortp_recv.c b/ortp_recv.c new file mode 100644 index 00000000..71c8b106 --- /dev/null +++ b/ortp_recv.c @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2005-2006 Andre Noll + * + * 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 + +#include "ortp.h" +#include "recv.h" +#include "ortp_recv.cmdline.h" + +#include "error.h" +#include "audiod.h" +#include "string.h" + +#define CHUNK_SIZE 128 * 1024 + +extern gint msg_to_buf(mblk_t *, char *, gint); + +/** + * data specific to the ortp receiver + * + * \sa receiver receiver_node + */ +struct private_ortp_recv_data { + +/** + * + * + * whether a header was received + * + * A flag indicating whether the ortp receiver already received a packet which + * contains the audio file header. + * + * This flag has no effect if the audio stream indicates that no extra headers + * will be sent (mp3). Otherwise, all data packets are dropped until the + * header is received. + */ +int have_header; +/** the ortp session this instance of the receiver belongs to */ +RtpSession *session; +/** the time the first data or header packet was received */ +struct timeval start; +/** ETA of the next chunk */ +struct timeval next_chunk; +/** number of consecutive bad chunks */ +int c_bad; +/** the current timestamp which is passed to the receiving function of libortp */ +guint32 timestamp; +/** \a timestamp increases by this amount */ +guint32 chunk_ts; +}; + +#if 1 +static void recalc_timestamp(struct receiver_node *rn) +{ + struct timeval now, tmp, min_delay = {0, 5000}; + struct private_ortp_recv_data *pord = rn->private_data; + + gettimeofday(&now, NULL); + tv_add(&now, &min_delay, &pord->next_chunk); + if (pord->start.tv_sec) { + int new_ts; + + tv_diff(&now, &pord->start, &tmp); + new_ts = rtp_session_time_to_ts(pord->session, tv2ms(&tmp)); +// if (pord->c_bad > 50) + pord->timestamp = new_ts; + } else + pord->timestamp = 0; + PARA_DEBUG_LOG("ts=%u, c_bad = %d\n", pord->timestamp, pord->c_bad); + +} +#endif + +static int ortp_recv_pre_select(struct receiver_node *rn, + __unused fd_set *rfds, __unused fd_set *wfds, + struct timeval *timeout) +{ + struct private_ortp_recv_data *pord = rn->private_data; + struct timeval now, tmp; + + gettimeofday(&now, NULL); + if (tv_diff(&now, &pord->next_chunk, &tmp) >= 0) { + tmp.tv_sec = 0; + tmp.tv_usec = 1000; + } + if (tv_diff(timeout, &tmp, NULL) > 0) + *timeout = tmp; + return -1; /* we did not modify the fd sets */ +} + +static void compute_next_chunk(struct timeval *now, unsigned chunk_time, + struct private_ortp_recv_data *pord) +{ + struct timeval chunk_tv = {0, chunk_time}; +// if (!pord->start.tv_sec) { +// pord->start = *now; +// tv_add(&chunk_tv, now, &pord->next_chunk); +// } else + { + struct timeval tmp; + tv_add(&chunk_tv, &pord->next_chunk, &tmp); + pord->next_chunk = tmp; + } + pord->timestamp += pord->chunk_ts; + PARA_DEBUG_LOG("next chunk (ts = %d) due at %lu:%lu\n", + pord->timestamp, pord->next_chunk.tv_sec, + pord->next_chunk.tv_usec); +} +/** \cond */ +#define BUF_TO_VAL(buf) (((unsigned)(buf)[1] & 0xff) + 256 * ((unsigned)(buf)[0] & 0xff)) +/** \endcond */ + + +static int ortp_recv_post_select(struct receiver_node *rn, int select_ret, + __unused fd_set *rfds, __unused fd_set *wfds) +{ + struct private_ortp_recv_data *pord = rn->private_data; + mblk_t *mp; + int ret, packet_type, stream_type; + char tmpbuf[CHUNK_SIZE + 3]; + struct timeval now; + unsigned chunk_time; + + gettimeofday(&now, NULL); + PARA_DEBUG_LOG("rn: %p, pord: %p, session: %p\n", rn, pord, pord->session); + if (pord->start.tv_sec) { + struct timeval diff; + if (tv_diff(&now, &pord->next_chunk, &diff) < 0) { + long unsigned diff_ms = tv2ms(&diff); + if (diff_ms > 5) { + PARA_DEBUG_LOG("too early: %lu ms left\n", + diff_ms); + return 1; + } + } + } + mp = rtp_session_recvm_with_ts(pord->session, pord->timestamp); + if (!mp) { + pord->c_bad++; + if ((pord->c_bad > 500 && pord->start.tv_sec) || pord->c_bad > 10000) + return -E_TOO_MANY_BAD_CHUNKS; +// PARA_NOTICE_LOG("did not receive buffer, ts=%u, c_bad = %d, to = %lu\n", +// pord->timestamp, pord->c_bad, tv2ms(&tmp)); + if (pord->c_bad > 100) + recalc_timestamp(rn); + return 1; + } + /* okay, we have a chunk of data */ + PARA_DEBUG_LOG("have it ts = %d, chunk_ts = %d, loaded: %d, bad: %d\n", + pord->timestamp, pord->chunk_ts, rn->loaded, pord->c_bad); + if (!pord->start.tv_sec) + pord->start = now; + ret = msg_to_buf(mp, tmpbuf, CHUNK_SIZE); + if (ret < ORTP_AUDIO_HEADER_LEN) { + if (ret < 0) + ret = -E_MSG_TO_BUF; + else + ret = 0; + goto err_out; + } + packet_type = READ_PACKET_TYPE(tmpbuf); + stream_type = READ_STREAM_TYPE(tmpbuf); + chunk_time = READ_CHUNK_TIME(tmpbuf); + pord->chunk_ts = READ_CHUNK_TS(tmpbuf); + PARA_DEBUG_LOG("packet type: %d, stream type: %d, chunk time: %u, chunk_ts: %u\n", packet_type, + stream_type, chunk_time, pord->chunk_ts); + switch (packet_type) { + unsigned header_len, payload_len; + case ORTP_EOF: + ret = 0; + goto err_out; + case ORTP_BOF: + PARA_INFO_LOG("bof (%d)\n", ret); + pord->have_header = 1; + /* fall through */ + case ORTP_DATA: + if (!pord->have_header && stream_type) /* can't use the data, wait for header */ + goto success; + if (ret + rn->loaded >= CHUNK_SIZE + ORTP_AUDIO_HEADER_LEN) { + ret = -E_OVERRUN; + goto err_out; + } + if (ret > ORTP_AUDIO_HEADER_LEN) { + memcpy(rn->buf + rn->loaded, tmpbuf + ORTP_AUDIO_HEADER_LEN, + ret - ORTP_AUDIO_HEADER_LEN); + rn->loaded += ret - ORTP_AUDIO_HEADER_LEN; + } + goto success; + case ORTP_HEADER: + header_len = READ_HEADER_LEN(tmpbuf); + PARA_DEBUG_LOG("header packet (%d bytes), header len: %d\n", ret, header_len); + if (!pord->have_header) { + pord->have_header = 1; + memcpy(rn->buf, tmpbuf + ORTP_AUDIO_HEADER_LEN, + ret - ORTP_AUDIO_HEADER_LEN); + rn->loaded = ret - ORTP_AUDIO_HEADER_LEN; + goto success; + } + if (header_len + ORTP_AUDIO_HEADER_LEN > ret) { + ret = -E_INVALID_HEADER; + goto err_out; + } + payload_len = ret - ORTP_AUDIO_HEADER_LEN - header_len; +// PARA_INFO_LOG("len: %d header_len: %d, payload_len: %d, loaded: %d\n", ret, +// header_len, payload_len, rn->loaded); + if (rn->loaded + payload_len > CHUNK_SIZE) { + ret = -E_OVERRUN; + goto err_out; + } + if (payload_len) + memcpy(rn->buf + rn->loaded, tmpbuf + + (ret - payload_len), payload_len); + rn->loaded += payload_len; + goto success; + } +success: + freemsg(mp); + pord->c_bad = 0; + compute_next_chunk(&now, chunk_time, pord); + return 1; +err_out: + freemsg(mp); + return ret; +} + +static void ortp_shutdown(void) +{ +// ortp_global_stats_display(); + ortp_exit(); +} + +static void ortp_recv_close(struct receiver_node *rn) +{ + struct private_ortp_recv_data *pord = rn->private_data; + + rtp_session_destroy(pord->session); + free(rn->private_data); + free(rn->buf); +} + +static void *ortp_recv_parse_config(int argc, char **argv) +{ + int ret; + + struct gengetopt_args_info *tmp = para_calloc(sizeof(struct gengetopt_args_info)); + + ret = ortp_recv_cmdline_parser(argc, argv, tmp)? -E_ORTP_SYNTAX : 1; + if (ret > 0) + return tmp; + free(tmp); + return NULL; +} + +static int ortp_recv_open(struct receiver_node *rn) +{ + struct private_ortp_recv_data *pord; + struct gengetopt_args_info *conf = rn->conf; + + rn->buf = para_calloc(CHUNK_SIZE); + + rn->private_data = para_calloc(sizeof(struct private_ortp_recv_data)); + pord = rn->private_data; + pord->session = rtp_session_new(RTP_SESSION_RECVONLY); + PARA_NOTICE_LOG("receiving from %s:%d\n", conf->host_arg, conf->port_arg); + rtp_session_set_local_addr(pord->session, conf->host_arg, conf->port_arg); + rtp_session_set_payload_type(pord->session, PAYLOAD_AUDIO_CONTINUOUS); + return 1; +} + +/** + * the init function of the ortp receiver + * + * \param r pointer to the receiver struct to initialize + * + * Initialize all function pointers of \a r and call libortp's ortp_init(). + */ +void ortp_recv_init(struct receiver *r) +{ + r->shutdown = ortp_shutdown; + r->open = ortp_recv_open; + r->close = ortp_recv_close; + r->pre_select = ortp_recv_pre_select; + r->post_select = ortp_recv_post_select; + r->parse_config = ortp_recv_parse_config; + + ortp_init(); + ortp_set_debug_file("oRTP", NULL); +} diff --git a/ortp_recv.ggo b/ortp_recv.ggo new file mode 100644 index 00000000..6902fede --- /dev/null +++ b/ortp_recv.ggo @@ -0,0 +1,3 @@ +section "ortp options" +option "host" i "ip or host to receive rtp packets from" string default="224.0.1.38" no +option "port" p "udp port." int typestr="portnumber" default="1500" no diff --git a/ortp_send.c b/ortp_send.c new file mode 100644 index 00000000..1521b63d --- /dev/null +++ b/ortp_send.c @@ -0,0 +1,334 @@ +/* + * Copyright (C) 2005-2006 Andre Noll + * + * 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 +#include "ortp.h" +#include "string.h" + +/** \cond convert in_addr to ascii */ +#define TARGET_ADDR(oc) inet_ntoa((oc)->addr) +/** \endcond */ + +extern struct gengetopt_args_info conf; + +/** describes one entry in the list of targets for the ortp sender */ +struct ortp_target { +/** address info */ + struct in_addr addr; +/** whether the ortp sender is activated */ + int status; +/** the ortp timestamp increases by this amount */ + int chunk_ts; +/** the currently used timestamp for this target */ + int last_ts; +/** the position of this target in the list of targets */ + struct list_head node; +/** the UDP port */ + int port; +/** non-zero if at least one chunk has been sent to this target */ + int streaming; +/** the session pointer from libortp */ + RtpSession *session; +}; + +static int numtargets; +static struct list_head targets; +static struct sender *self; + +static void ortp_delete_target(struct ortp_target *ot, const char *msg) +{ + PARA_NOTICE_LOG("deleting %s:%d (%s) from list\n", TARGET_ADDR(ot), + ot->port, msg); + if (ot->session) { + rtp_session_destroy(ot->session); + ot->session = NULL; + } + numtargets--; + list_del(&ot->node); + free(ot); +} + +static void ortp_send_buf(char *buf, int len, long unsigned chunks_sent) +{ + struct ortp_target *ot, *tmp; + int ret; + + list_for_each_entry_safe(ot, tmp, &targets, node) { + struct timeval now; + int ts; + gettimeofday(&now, NULL); + if (!ot->session) + continue; + WRITE_CHUNK_TS(buf, ot->chunk_ts); + ts = ot->chunk_ts * chunks_sent; + ret = rtp_session_send_with_ts(ot->session, buf, len, ts); + ot->last_ts = ts; + if (ret < 0) + ortp_delete_target(ot, "send error"); + if (ret != len +12) + PARA_NOTICE_LOG("short write %d\n", ret); + } +} + +static void ortp_init_session(struct ortp_target *ot) +{ + RtpSession *s; + int ret; + + PARA_NOTICE_LOG("sending to udp %s:%d\n", TARGET_ADDR(ot), ot->port); + ot->session = rtp_session_new(RTP_SESSION_SENDONLY); + if (!ot->session) + return; + s = ot->session; +// rtp_session_set_jitter_compensation(ot->session, 100); + /* always successful */ + rtp_session_set_send_payload_type(s, PAYLOAD_AUDIO_CONTINUOUS); + ret = rtp_session_set_remote_addr(s, TARGET_ADDR(ot), ot->port); + if (ret < 0) { + rtp_session_destroy(ot->session); + ot->session = NULL; + } +} + +/* called by afs */ +static void ortp_shutdown_targets(void) +{ + char buf[2]; + struct ortp_target *ot, *tmp; + + buf[0] = ORTP_EOF; + list_for_each_entry_safe(ot, tmp, &targets, node) { + if (!ot->session || !ot->streaming) + continue; + PARA_INFO_LOG("sending eof to ortp target %s:%d, ts = %d\n", TARGET_ADDR(ot), ot->port, + ot->last_ts); + rtp_session_send_with_ts(ot->session, buf, 1, ot->last_ts); + ot->streaming = 0; + ot->chunk_ts = 0; + rtp_session_reset(ot->session); + } +} + +static int need_extra_header(struct audio_format *af, long unsigned chunks_sent) +{ + /* FIXME: No need to compute this on every run */ + int mod = conf.ortp_header_interval_arg / (tv2ms(&af->chunk_tv) + 1); + if (mod && (chunks_sent % mod)) + return 0; + return 1; +} + +static void ortp_send(struct audio_format *af, long unsigned current_chunk, + long unsigned chunks_sent, const char *buf, size_t len) +{ + struct ortp_target *ot, *tmp; + size_t sendbuf_len; + size_t header_len = 0; + int packet_type = ORTP_DATA, stream_type = af && af->get_header_info; /* header stream? */ + char *sendbuf, *header_buf = NULL; + + if (self->status != SENDER_ON) + return; + list_for_each_entry_safe(ot, tmp, &targets, node) { + if (!ot->session) { + ortp_init_session(ot); + if (!ot->session) + continue; + } + if (!ot->chunk_ts) + ot->chunk_ts = rtp_session_time_to_ts(ot->session, tv2ms(&af->chunk_tv)); +// PARA_DEBUG_LOG("len: %d, af: %p, ts: %d\n", len, af, ot->chunk_ts); + ot->streaming = 1; + } + if (list_empty(&targets)) + return; + if (stream_type) { + header_buf = af->get_header_info(&header_len); + if (!need_extra_header(af, chunks_sent)) + header_len = 0; + } + sendbuf_len = ORTP_AUDIO_HEADER_LEN + header_len + len; + sendbuf = para_malloc(sendbuf_len); + if (!current_chunk) + packet_type = ORTP_BOF; + else if (header_len) + packet_type = ORTP_HEADER; + WRITE_PACKET_TYPE(sendbuf, packet_type); + WRITE_CHUNK_TIME(sendbuf, af->chunk_tv.tv_usec); + WRITE_STREAM_TYPE(sendbuf, stream_type); + WRITE_HEADER_LEN(sendbuf, header_len); + if (header_len) + memcpy(sendbuf + ORTP_AUDIO_HEADER_LEN, header_buf, + header_len); + memcpy(sendbuf + ORTP_AUDIO_HEADER_LEN + header_len, buf, len); + ortp_send_buf(sendbuf, sendbuf_len, chunks_sent); + free(sendbuf); +} + +static int ortp_com_on(struct sender_command_data *scd) +{ + + self->status = SENDER_ON; + return 1; +} + +static int ortp_com_off(struct sender_command_data *scd) +{ + ortp_shutdown_targets(); + self->status = SENDER_OFF; + return 1; +} + +static int ortp_com_delete(struct sender_command_data *scd) +{ + char *a = para_strdup(inet_ntoa(scd->addr)); + struct ortp_target *ot, *tmp; + list_for_each_entry_safe(ot, tmp, &targets, node) { + if (scd->port != ot->port) + continue; + if (strcmp(TARGET_ADDR(ot), a)) + continue; + ortp_delete_target(ot, "com_delete"); + } + return 1; +} + +static void ortp_add_target(int port, struct in_addr *addr) +{ + struct ortp_target *ot = para_calloc(sizeof(struct ortp_target)); + ot->port = port; + ot->addr = *addr; + PARA_INFO_LOG("adding to target list (%s:%d)\n", + TARGET_ADDR(ot), ot->port); + list_add(&ot->node, &targets); +} + +static int ortp_com_add(struct sender_command_data *scd) +{ + int port = (scd->port > 0)? scd->port : conf.ortp_default_port_arg; + ortp_add_target(port, &scd->addr); + return 1; +} + +static char *ortp_info(void) +{ + struct ortp_target *ot; + char *ret, *tgts = NULL; + + list_for_each_entry(ot, &targets, node) { + char *tmp = make_message("%s%s:%d ", tgts? tgts : "", + TARGET_ADDR(ot), ot->port); + free(tgts); + tgts = tmp; + } + ret = make_message( + "ortp status: %s\n" + "ortp default port: udp %d\n" + "ortp targets: %s\n", + (self->status == SENDER_ON)? "on" : "off", + conf.ortp_default_port_arg, + tgts? tgts : "(none)" + ); + free(tgts); + return ret; +} + +static void ortp_init_target_list(void) +{ + int i; + + INIT_LIST_HEAD(&targets); + for (i = 0; i < conf.ortp_target_given; i++) { + char *arg = para_strdup(conf.ortp_target_arg[i]); + char *p = strchr(arg, ':'); + int port; + struct in_addr addr; + + if (!p) + goto err; + *p = '\0'; + if (!inet_aton(arg, &addr)) + goto err; + port = atoi(++p); + if (port < 0 || port > 65535) + port = conf.ortp_default_port_arg; + ortp_add_target(port, &addr); + goto success; +err: + PARA_CRIT_LOG("syntax error for ortp_target option " + "#%d, ignoring\n", i); +success: + free(arg); + continue; + } +} + +static void ortp_pre_select(struct audio_format *af, int *max_fileno, + fd_set *rfds, fd_set *wfds) +{ + return; +} + +static char *ortp_help(void) +{ + return make_message( + "usage: {on|off}\n" + "usage: {add|delete} IP port\n" + "example: add 224.0.1.38 1500 (LAN-streaming)\n" + ); +} + +/** + * the init function of para_server's ortp sender + * + * \param s pointer to the http sender struct + * + * It initializes all function pointers of \a s and the list of ortp targets. + */ +void ortp_send_init(struct sender *s) +{ + ortp_init(); + ortp_set_debug_file("oRTP", NULL); + INIT_LIST_HEAD(&targets); + s->info = ortp_info; + s->help = ortp_help; + s->send = ortp_send; + s->pre_select = ortp_pre_select; + s->post_select = NULL; + s->shutdown_clients = ortp_shutdown_targets; + s->client_cmds[SENDER_ON] = ortp_com_on; + s->client_cmds[SENDER_OFF] = ortp_com_off; + s->client_cmds[SENDER_DENY] = NULL; + s->client_cmds[SENDER_ALLOW] = NULL; + s->client_cmds[SENDER_ADD] = ortp_com_add; + s->client_cmds[SENDER_DELETE] = ortp_com_delete; + self = s; + s->status = SENDER_OFF; + ortp_init_target_list(); + if (!conf.ortp_no_autostart_given) + s->status = SENDER_ON; + PARA_DEBUG_LOG("%s", "ortp sender init complete\n"); +} diff --git a/para.h b/para.h new file mode 100644 index 00000000..049262d3 --- /dev/null +++ b/para.h @@ -0,0 +1,227 @@ +/* + * Copyright (C) 1997-2006 Andre Noll + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include /* time(), localtime() */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* needed by create_pf_socket */ +#include "gcc-compat.h" + +/* some internal constants */ +#define STRINGSIZE 4096 +#define MAXLINE 255 + + +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#define ABS(a) ((a) > 0 ? (a) : -(a)) + +/* Loglevels */ +#define DEBUG 1 +#define INFO 2 +#define NOTICE 3 +#define WARNING 4 +#define ERROR 5 +#define CRIT 6 +#define EMERG 7 + + +#define COMPILE_TIME_LOGLEVEL 0 +#if DEBUG > COMPILE_TIME_LOGLEVEL +#define PARA_DEBUG_LOG(f,...) para_log(DEBUG, "%s: " f, __FUNCTION__, __VA_ARGS__) +#else +#define PARA_DEBUG_LOG(...) +#endif + +#if INFO > COMPILE_TIME_LOGLEVEL +#define PARA_INFO_LOG(f,...) para_log(INFO, "%s: " f, __FUNCTION__, __VA_ARGS__) +#else +#define PARA_INFO_LOG(...) +#endif + +#if NOTICE > COMPILE_TIME_LOGLEVEL +#define PARA_NOTICE_LOG(f,...) para_log(NOTICE, "%s: " f, __FUNCTION__, __VA_ARGS__) +#else +#define PARA_NOTICE_LOG(...) +#endif + +#if WARNING > COMPILE_TIME_LOGLEVEL +#define PARA_WARNING_LOG(f,...) para_log(WARNING, "%s: " f, __FUNCTION__, __VA_ARGS__) +#else +#define PARA_WARNING_LOG(...) +#endif + +#if ERROR > COMPILE_TIME_LOGLEVEL +#define PARA_ERROR_LOG(f,...) para_log(ERROR, "%s: " f, __FUNCTION__, __VA_ARGS__) +#else +#define PARA_ERROR_LOG(...) +#endif + +#if CRIT > COMPILE_TIME_LOGLEVEL +#define PARA_CRIT_LOG(f,...) para_log(CRIT, "%s: " f, __FUNCTION__, __VA_ARGS__) +#else +#define PARA_CRIT_LOG(...) +#endif + +#if EMERG > COMPILE_TIME_LOGLEVEL +#define PARA_EMERG_LOG(f,...) para_log(EMERG, "%s: " f, __FUNCTION__, __VA_ARGS__) +#else +#define PARA_EMERG_LOG(...) +#endif + +#define COPYRIGHT "Copyright (c) 1997-2006 by Andre Noll" + +#define LICENSE "This is free software with ABSOLUTELY NO WARRANTY. " \ + "See COPYING for details." + +#define AWAITING_DATA_MSG "\nAwaiting Data." +#define PROCEED_MSG "\nProceed.\n" +#define PROCEED_MSG_LEN strlen(PROCEED_MSG) +#define EOC_MSG "\nEnd of Command." +#define CHALLENGE_RESPONSE_MSG "challenge_response:" + +/* gui_common */ +int para_open_audiod_pipe(char *); +int read_audiod_pipe(int, void (*)(char *)); + +/* exec */ +int file_exists(const char *); +int para_exec(pid_t *, const char *, char *const [], int *); +int para_exec_cmdline_pid(pid_t *, char *, int *); + +/* signal */ +int para_signal_init(void); +int para_install_sighandler(int); +void para_reap_children(void); +pid_t para_reap_child(void); +int para_next_signal(void); + +/* time */ +int tv_diff(const struct timeval *b, const struct timeval *a, struct timeval *diff); +long unsigned tv2ms(const struct timeval*); +void d2tv(double, struct timeval*); +void tv_add(const struct timeval*, const struct timeval *, struct timeval *); +void tv_scale(const unsigned long, const struct timeval *, struct timeval *); +void tv_divide(const unsigned long div, const struct timeval *tv, + struct timeval *result); +int tv_convex_combination(const long a, const struct timeval *tv1, + const long b, const struct timeval *tv2, + struct timeval *result); +void ms2tv(const long unsigned n, struct timeval *tv); + +/* stat */ +enum { + SI_STATUS_BAR, SI_STATUS, SI_NUM_PLAYED, + SI_MTIME, SI_LENGTH_MIN, SI_LENGTH_SEC, + SI_FILE_SIZE, SI_STATUS_FLAGS, SI_FORMAT, + SI_SCORE, SI_AUDIO_INFO1, SI_AUDIO_INFO2, + SI_AUDIO_INFO3, SI_DBINFO1, SI_DBINFO2, + SI_DBINFO3, SI_DECODER_FLAGS, SI_AUDIOD_STATUS, + SI_PLAY_TIME, SI_UPTIME, SI_OFFSET, + SI_LENGTH, SI_STREAM_START, SI_CURRENT_TIME, + SI_AUDIOD_UPTIME, SI_DBTOOL, +}; +#define NUM_STAT_ITEMS (SI_DBTOOL + 1) +int stat_line_valid(const char *); +void stat_client_write(char *msg); +int stat_client_add(int); +void dump_empty_status(void); +unsigned for_each_line(char *, int, void (*)(char *), int); + +struct stat_item_data { + char *prefix, *postfix; + unsigned x, y, len; + int fg, bg, align; +}; + +/* gui_theme */ +struct gui_theme { + char *name; + char *author; + int sb_fg, sb_bg; + int cmd_fg, cmd_bg; + int output_fg, output_bg; + int msg_fg, msg_bg; + int err_msg_fg, err_msg_bg; + int welcome_fg, welcome_bg; + int sep_fg, sep_bg; + char *sep_str; + int default_fg, default_bg; + + int top_lines_default, top_lines_min; + int lines_min, cols_min; + struct stat_item_data data[NUM_STAT_ITEMS]; +}; + +void init_theme(int i, struct gui_theme *); +void next_theme(struct gui_theme *); +void prev_theme(struct gui_theme *); +#define LEFT 1 +#define RIGHT 2 +#define CENTER 3 + + +__printf_2_3 void para_log(int, char*, ...); + +/* taken from printf man page */ +#define PARA_VSPRINTF(fmt, p) \ +{ \ + int n, size = 100; \ + p = para_malloc(size); \ + while (1) { \ + va_list ap; \ + /* Try to print in the allocated space. */ \ + va_start(ap, fmt); \ + n = vsnprintf(p, size, fmt, ap); \ + va_end(ap); \ + /* If that worked, return the string. */ \ + if (n > -1 && n < size) \ + break; \ + /* Else try again with more space. */ \ + if (n > -1) /* glibc 2.1 */ \ + size = n + 1; /* precisely what is needed */ \ + else /* glibc 2.0 */ \ + size *= 2; /* twice the old size */ \ + p = para_realloc(p, size); \ + } \ +} + + + diff --git a/pics/paraslash/default.jpg b/pics/paraslash/default.jpg new file mode 100644 index 00000000..2de4076b Binary files /dev/null and b/pics/paraslash/default.jpg differ diff --git a/pics/screenshots/gui-2004-07-11.png b/pics/screenshots/gui-2004-07-11.png new file mode 100644 index 00000000..51b1c3f7 Binary files /dev/null and b/pics/screenshots/gui-2004-07-11.png differ diff --git a/pics/screenshots/gui-2004-09-02.png b/pics/screenshots/gui-2004-09-02.png new file mode 100644 index 00000000..ad6c9f58 Binary files /dev/null and b/pics/screenshots/gui-2004-09-02.png differ diff --git a/pics/screenshots/gui-2005-11-12.png b/pics/screenshots/gui-2005-11-12.png new file mode 100644 index 00000000..b3decd4c Binary files /dev/null and b/pics/screenshots/gui-2005-11-12.png differ diff --git a/pics/screenshots/gui-old.png b/pics/screenshots/gui-old.png new file mode 100644 index 00000000..74a81d52 Binary files /dev/null and b/pics/screenshots/gui-old.png differ diff --git a/pics/screenshots/loglevel1-2004-07-28.txt b/pics/screenshots/loglevel1-2004-07-28.txt new file mode 100644 index 00000000..2995f0b7 --- /dev/null +++ b/pics/screenshots/loglevel1-2004-07-28.txt @@ -0,0 +1,43 @@ +Jul 28 08:05:15 1: (29586) afs_send_chunk: Song finished (flags:8) +Jul 28 08:05:15 1: (29586) get_song: Getting next song +Jul 28 08:05:16 1: (29586) get_song: Opening /home/mp3/checked/cd_37/Rodgau_Monotones__Is_Mir_Egal.mp3 +Jul 28 08:05:16 1: (29586) update_mmd: initialising mmd struct +Jul 28 08:05:16 4: (29586) update_mmd: Next song: /home/mp3/checked/cd_37/Rodgau_Monotones__Is_Mir_Egal.mp3 +Jul 28 08:05:16 3: (29586) update_mmd: size: 4664869, mtime: Tue Jul 13 18:50:40 2004 +Jul 28 08:05:16 3: (29586) update_mmd: current stream: wake, num_played: 35 +Jul 28 08:05:16 3: (29586) update_mmd: length: 3:53, 160 KBit/s, 44100 KHz (joint stereo) +Jul 28 08:05:16 1: (29586) update_mmd: mmd.length=233, mmd.size=4664869 +Jul 28 08:05:16 1: (29586) update_dbinfo: getting dbtool info /home/mp3/checked/cd_37/Rodgau_Monotones__Is_Mir_Egal.mp3 +Jul 28 08:05:16 3: (29586) update_dbinfo: dir: /home/mp3/checked/cd_37 +Jul 28 08:05:16 3: (29586) update_dbinfo: last played: 4 day(s) ago. Numplayed: 5, Pic_Id: 314 +Jul 28 08:05:16 3: (29586) update_dbinfo: attributes: fast fun deutsch rock +Jul 28 08:05:16 1: (29586) update_dbinfo: Read 116 byte of info and put 140 byte into mmd->dbinfo +Jul 28 08:05:16 1: (29586) update_mmd: calling dbtool to update song +Jul 28 08:05:16 1: (29586) main: Caught signal 17 +Jul 28 08:05:16 1: (29586) handle_sigchld: child 3956 exited. Exit status: 0 +Jul 28 08:05:16 1: (29586) handle_sigchld: child 3959 exited. Exit status: 0 +Jul 28 08:05:16 1: (29586) main: Caught signal 17 +Jul 28 08:05:16 1: (29586) main: Caught signal 17 +Jul 28 08:05:16 1: (29586) handle_sigchld: child 3961 exited. Exit status: 0 +Jul 28 08:05:16 4: (29586) main: Got connection from 192.168.0.7 +Jul 28 08:05:16 1: (3963) handle_connect: Received auth request for user maan +Jul 28 08:05:16 1: (3963) open_user_list: Opening config file /home/maan/.paraslash/server.users +Jul 28 08:05:16 1: (3963) get_user: found entry for maan +Jul 28 08:05:16 1: (3963) get_user: found 4 perm entries +Jul 28 08:05:16 1: (3963) get_user: tmp[3]=AFS_WRITE +Jul 28 08:05:16 1: (3963) get_user: tmp[2]=AFS_READ +Jul 28 08:05:16 1: (3963) get_user: tmp[1]=DB_WRITE +Jul 28 08:05:16 1: (3963) get_user: tmp[0]=DB_READ +Jul 28 08:05:16 1: (3964) encrypt: dup2ing stdin +Jul 28 08:05:16 1: (3964) encrypt: dup2ing stdout +Jul 28 08:05:16 1: (3963) encrypt: Writing challenge to pipe (11 bytes) +Jul 28 08:05:16 1: (3963) encrypt: Reading line from pipe +Jul 28 08:05:16 1: (3963) encrypt: Read 64 byte from pipe +Jul 28 08:05:16 1: (3963) handle_connect: Sending 64 byte challenge for maan +Jul 28 08:05:16 3: (3963) handle_connect: Good auth for maan (1894092672) +Jul 28 08:05:16 1: (3963) parse_cmd: Found command pic. Perms: 1 +Jul 28 08:05:16 1: (3963) check_perms: check_perms +Jul 28 08:05:16 4: (3963) handle_connect: executing command handler for command "pic" +Jul 28 08:05:16 1: (29586) main: Caught signal 17 +Jul 28 08:05:16 1: (29586) handle_sigchld: child 3963 exited. Exit status: 0 + diff --git a/pics/screenshots/loglevel1-2005-03-23.txt b/pics/screenshots/loglevel1-2005-03-23.txt new file mode 100644 index 00000000..c1438f9a --- /dev/null +++ b/pics/screenshots/loglevel1-2005-03-23.txt @@ -0,0 +1,65 @@ +Mar 23 20:47:26 3: (19185) do_inits: welcome to para_server 0.1.6-dev (Wed Mar 23 20:39:23 MET 2005) +Mar 23 20:47:26 3: (19185) +Mar 23 20:47:26 1: (19185) do_inits: using loglevel 1 +Mar 23 20:47:26 4: (19185) init_dbtool: initializing database tool +Mar 23 20:47:26 1: (19185) init_mysql_server: connecting: maan@localhost:3306 +Mar 23 20:47:26 3: (19185) init_mysql_server: successfully connected to mysql server +Mar 23 20:47:26 3: (19185) init_dbtool: initialized mysql +Mar 23 20:47:26 4: (19185) do_inits: initializing audio file sender +Mar 23 20:47:26 3: (19185) afs_init: checking audio formats +Mar 23 20:47:26 1: (19185) afs_init: supported audio format: mp3 +Mar 23 20:47:26 1: (19185) setup_stream_command: 1 stream write command given, checking for mp3 +Mar 23 20:47:26 3: (19185) setup_stream_command: using "poc-fec -q -" for files of type mp3 (default) +Mar 23 20:47:26 1: (19185) afs_init: supported audio format: ogg +Mar 23 20:47:26 1: (19185) setup_stream_command: 1 stream write command given, checking for ogg +Mar 23 20:47:26 3: (19185) setup_stream_command: using "/home/maan/para/para_ovsend -r /home/maan/.paraslash/ogg_fifo.p133" for files of type ogg +Mar 23 20:47:26 1: (19185) afs_init: supported audio formats: mp3 ogg +Mar 23 20:47:26 4: (19185) setup_signal_handling: setting up signal handlers +Mar 23 20:47:26 1: (19185) para_install_sighandler: catching signal 2 +Mar 23 20:47:26 1: (19185) para_install_sighandler: catching signal 15 +Mar 23 20:47:26 1: (19185) para_install_sighandler: catching signal 1 +Mar 23 20:47:26 1: (19185) para_install_sighandler: catching signal 17 +Mar 23 20:47:26 4: (19185) do_inits: initializing networking +Mar 23 20:47:26 3: (19185) init_network: init network socket +Mar 23 20:47:26 4: (19185) do_inits: init complete +Mar 23 20:47:26 1: (19185) afs_mainloop: not playing +Mar 23 20:47:29 4: (19185) main: got connection from 192.168.0.8, forking +Mar 23 20:47:29 1: (19185) afs_mainloop: not playing +Mar 23 20:47:29 1: (19187) handle_connect: Received auth request for user maan +Mar 23 20:47:29 1: (19187) open_user_list: Opening user list /home/maan/.paraslash/server.users +Mar 23 20:47:29 1: (19187) get_user: found entry for maan +Mar 23 20:47:29 1: (19187) get_user: found 4 perm entries +Mar 23 20:47:29 3: (19187) encrypt: writing challenge to pipe (11 bytes) +Mar 23 20:47:29 1: (19187) encrypt: reading response +Mar 23 20:47:29 1: (19187) encrypt: read 64 byte from pipe +Mar 23 20:47:29 1: (19187) handle_connect: sending 64 byte challenge +Mar 23 20:47:29 3: (19187) handle_connect: good auth for maan (3859497713) +Mar 23 20:47:29 1: (19187) parse_cmd: found command play. +Mar 23 20:47:29 1: (19187) check_perms: checking permissions +Mar 23 20:47:29 4: (19187) handle_connect: executing command handler for command "play" +Mar 23 20:47:29 3: (19187) handle_connect: command handler returned success +Mar 23 20:47:29 1: (19185) afs_mainloop: no audio file +Mar 23 20:47:29 1: (19185) real_query: select def from streams where name = 'current_stream' +Mar 23 20:47:29 1: (19185) real_query: select def from streams where name='gulp' +Mar 23 20:47:29 1: (19185) real_query: select concat(dir.dir, '/', dir.name) from data,dir where dir.name = data.name and ( (dir.name like '%G.U.L.P%')) order by -((FLOOR((UNIX_TIMESTAMP(now())-UNIX_TIMESTAMP(data.Lastplayed))/60) / 1440 - sqrt(data.Numplayed))) +Mar 23 20:47:29 3: (19185) get_song: trying /home/mp3/checked/cd_20/The_G.U.L.P.__U-Bahn.mp3 +Mar 23 20:47:29 1: (19185) guess_audio_format: might be mp3 +Mar 23 20:47:29 3: (19185) get_file_info: valid mp3 file +Mar 23 20:47:29 4: (19185) update_mmd: next audio file: /home/mp3/checked/cd_20/The_G.U.L.P.__U-Bahn.mp3 +Mar 23 20:47:29 1: (19185) real_query: select def from streams where name = 'current_stream' +Mar 23 20:47:29 1: (19185) real_query: select concat('lastplayed: ', (to_days(now()) - to_days(lastplayed)),' day(s). numplayed: ', numplayed, ', pic: ', pic_id) from data where name = 'The_G.U.L.P.__U-Bahn.mp3' +Mar 23 20:47:29 1: (19185) real_query: desc data +Mar 23 20:47:29 1: (19185) real_query: select * from data where name='The_G.U.L.P.__U-Bahn.mp3' +Mar 23 20:47:29 1: (19185) real_query: select dir from dir where name = 'The_G.U.L.P.__U-Bahn.mp3' +Mar 23 20:47:29 1: (19185) real_query: select def from streams where name='gulp' +Mar 23 20:47:29 1: (19185) real_query: select (FLOOR((UNIX_TIMESTAMP(now())-UNIX_TIMESTAMP(data.Lastplayed))/60) / 1440 - sqrt(data.Numplayed)) from data where name = 'The_G.U.L.P.__U-Bahn.mp3' +Mar 23 20:47:29 1: (19185) update_mmd: updating shared memory area +Mar 23 20:47:29 1: (19185) real_query: update data set lastplayed = now() where name='The_G.U.L.P.__U-Bahn.mp3' +Mar 23 20:47:29 1: (19185) real_query: select numplayed from data where name = 'The_G.U.L.P.__U-Bahn.mp3' +Mar 23 20:47:29 1: (19185) real_query: update data set numplayed=38 where name='The_G.U.L.P.__U-Bahn.mp3' +Mar 23 20:47:29 1: (19185) get_song: success +Mar 23 20:47:29 1: (19185) afs_mainloop: open stream +Mar 23 20:47:29 3: (19185) afs_open_stream_writer: opening mp3 stream_writer (poc-fec -q -) +Mar 23 20:47:29 1: (19185) afs_open_stream_writer: mp3 stream writer pid: 19189, fd: 10 +Mar 23 20:47:29 1: (19185) para_next_signal: next signal: 17 +Mar 23 20:47:29 1: (19185) para_reap_child: child 19187 exited. Exit status: 0 diff --git a/pics/screenshots/para_audiod-startup.txt b/pics/screenshots/para_audiod-startup.txt new file mode 100644 index 00000000..1296e8b1 --- /dev/null +++ b/pics/screenshots/para_audiod-startup.txt @@ -0,0 +1,33 @@ +Jan 22 03:17:31 meins 2 log_welcome: welcome to para_audiod cvs (Sun Jan 22 02:51:13 MET 2006) +Jan 22 03:17:31 meins 2 init_stream_io: initializing http receiver +Jan 22 03:17:31 meins 2 init_stream_io: initializing ortp receiver +Jan 22 03:17:31 meins 2 init_stream_io: initializing wav filter +Jan 22 03:17:31 meins 2 init_stream_io: initializing compress filter +Jan 22 03:17:31 meins 2 init_stream_io: initializing mp3dec filter +Jan 22 03:17:31 meins 2 init_stream_io: initializing oggdec filter +Jan 22 03:17:31 meins 2 add_filter: mp3 filter 1: mp3dec +Jan 22 03:17:31 meins 2 add_filter: mp3 filter 2: compress +Jan 22 03:17:31 meins 2 add_filter: mp3 filter 3: wav +Jan 22 03:17:31 meins 2 add_filter: ogg filter 1: oggdec +Jan 22 03:17:31 meins 2 add_filter: ogg filter 2: compress +Jan 22 03:17:31 meins 2 add_filter: ogg filter 3: wav +Jan 22 03:17:31 meins 2 clear_slot: clearing slot 0 +Jan 22 03:17:31 meins 2 clear_slot: clearing slot 1 +Jan 22 03:17:31 meins 2 clear_slot: clearing slot 2 +Jan 22 03:17:31 meins 2 clear_slot: clearing slot 3 +Jan 22 03:17:31 meins 2 clear_slot: clearing slot 4 +Jan 22 03:17:31 meins 2 init_grabbing: grab init +Jan 22 03:17:31 meins 2 setup_signal_handling: signal pipe: fd 4 +Jan 22 03:17:31 meins 2 daemon_init: daemonizing +Jan 22 03:17:31 meins 3 open_stat_pipe: stat pipe opened, fd 6 +Jan 22 03:17:31 meins 3 audiod_get_socket: connecting to local socket /var/paraslash/audiod_socket.meins +Jan 22 03:17:32 meins 2 handle_connect: pid: 8807, uid: 409, gid: 100, ret: 254, buf: stat +Jan 22 03:17:32 meins 2 handle_connect: argv[0]: stat +Jan 22 03:17:32 meins 2 stat_client_add: adding client on fd 8 +Jan 22 03:17:32 meins 2 dump_stat_client_list: stat client on fd 8 +Jan 22 03:17:32 meins 2 handle_connect: pid: 8806, uid: 409, gid: 100, ret: 254, buf: stat +Jan 22 03:17:32 meins 2 handle_connect: argv[0]: stat +Jan 22 03:17:32 meins 2 stat_client_add: adding client on fd 9 +Jan 22 03:17:32 meins 2 dump_stat_client_list: stat client on fd 9 +Jan 22 03:17:32 meins 2 dump_stat_client_list: stat client on fd 8 +Jan 22 03:17:46 meins 2 compute_time_diff: time diff (cur/avg): 0:1/0:0 diff --git a/pics/screenshots/para_krell-2005-02.png b/pics/screenshots/para_krell-2005-02.png new file mode 100644 index 00000000..1aa25d81 Binary files /dev/null and b/pics/screenshots/para_krell-2005-02.png differ diff --git a/pics/screenshots/para_server-startup.txt b/pics/screenshots/para_server-startup.txt new file mode 100644 index 00000000..f0a6292c --- /dev/null +++ b/pics/screenshots/para_server-startup.txt @@ -0,0 +1,56 @@ +Jan 22 03:08:46 2: (27321) log_welcome: welcome to para_server cvs (Sun Jan 22 02:51:13 MET 2006) +Jan 22 03:08:46 1: (27321) log_welcome: using loglevel 1 +Jan 22 03:08:46 2: (27321) daemon_init: daemonizing +Jan 22 03:08:46 3: (27322) init_dbtool: initializing mysql database tool +Jan 22 03:08:46 1: (27322) init_mysql_server: connecting: maan@localhost:3306 +Jan 22 03:08:46 2: (27322) init_mysql_server: successfully connected to mysql server +Jan 22 03:08:46 2: (27322) init_dbtool: initialized mysql +Jan 22 03:08:46 3: (27322) do_inits: initializing audio file sender +Jan 22 03:08:46 1: (27322) afs_init: supported audio formats: mp3 ogg +Jan 22 03:08:46 3: (27322) afs_init: initializing mp3 handler +Jan 22 03:08:46 3: (27322) afs_init: initializing ogg handler +Jan 22 03:08:46 2: (27322) afs_init: announce timeval: 0:300000 +Jan 22 03:08:46 3: (27322) afs_init: initializing http sender +Jan 22 03:08:46 2: (27322) add_perm_list_entry: adding 192.168.0.1/32 to access list +Jan 22 03:08:46 2: (27322) add_perm_list_entry: adding 192.168.0.4/32 to access list +Jan 22 03:08:46 2: (27322) add_perm_list_entry: adding 192.168.0.8/32 to access list +Jan 22 03:08:46 2: (27322) init_tcp_socket: listening on port 8000, fd 5 +Jan 22 03:08:46 1: (27322) para_http_init: http sender init complete +Jan 22 03:08:46 3: (27322) afs_init: initializing ortp sender +Jan 22 03:08:46 2: (27322) ortp_add_target: adding to target list (224.0.1.38:1500) +Jan 22 03:08:46 1: (27322) para_ortp_init: ortp sender init complete +Jan 22 03:08:46 3: (27322) setup_signal_handling: setting up signal handlers +Jan 22 03:08:46 1: (27322) para_install_sighandler: catching signal 2 +Jan 22 03:08:46 1: (27322) para_install_sighandler: catching signal 15 +Jan 22 03:08:46 1: (27322) para_install_sighandler: catching signal 1 +Jan 22 03:08:46 1: (27322) para_install_sighandler: catching signal 17 +Jan 22 03:08:46 3: (27322) do_inits: initializing tcp command socket +Jan 22 03:08:46 2: (27322) init_tcp_socket: listening on port 2990, fd 8 +Jan 22 03:08:46 3: (27322) do_inits: init complete +Jan 22 03:08:46 1: (27322) status_refresh: 0 events, forcing status update, af = -1 +Jan 22 03:08:47 2: (27322) main: got connection from 192.168.0.8, forking +Jan 22 03:08:47 1: (27328) close_listed_fds: closing fd 5 +Jan 22 03:08:47 1: (27328) handle_connect: received rc4 request for user maan +Jan 22 03:08:47 1: (27328) open_user_list: opening user list /home/maan/.paraslash/server.users +Jan 22 03:08:47 1: (27328) get_user: found entry for maan +Jan 22 03:08:47 1: (27328) get_user: found 4 perm entries +Jan 22 03:08:47 1: (27328) handle_connect: sending 64 byte challenge +Jan 22 03:08:47 2: (27328) handle_connect: good auth for maan (1359837743) +Jan 22 03:08:47 1: (27328) init_rc4_keys: rc4 keys initialized (198:20) +Jan 22 03:08:47 2: (27328) handle_connect: rc4 encrytion activated +Jan 22 03:08:47 1: (27328) parse_cmd: found command stat +Jan 22 03:08:47 1: (27328) check_perms: checking permissions +Jan 22 03:08:47 3: (27328) handle_connect: calling com_stat() for maan@192.168.0.8 +Jan 22 03:08:47 2: (27322) main: got connection from 192.168.0.4, forking +Jan 22 03:08:47 1: (27329) close_listed_fds: closing fd 5 +Jan 22 03:08:47 1: (27329) handle_connect: received rc4 request for user maan +Jan 22 03:08:47 1: (27329) open_user_list: opening user list /home/maan/.paraslash/server.users +Jan 22 03:08:47 1: (27329) get_user: found entry for maan +Jan 22 03:08:47 1: (27329) get_user: found 4 perm entries +Jan 22 03:08:47 1: (27329) handle_connect: sending 64 byte challenge +Jan 22 03:08:47 2: (27329) handle_connect: good auth for maan (1661875263) +Jan 22 03:08:47 1: (27329) init_rc4_keys: rc4 keys initialized (246:109) +Jan 22 03:08:47 2: (27329) handle_connect: rc4 encrytion activated +Jan 22 03:08:47 1: (27329) parse_cmd: found command stat +Jan 22 03:08:47 1: (27329) check_perms: checking permissions +Jan 22 03:08:47 3: (27329) handle_connect: calling com_stat() for maan@192.168.0.4 diff --git a/pics/screenshots/para_slider-2004-12.png b/pics/screenshots/para_slider-2004-12.png new file mode 100644 index 00000000..f997fbad Binary files /dev/null and b/pics/screenshots/para_slider-2004-12.png differ diff --git a/pics/screenshots/sdl_gui.jpg b/pics/screenshots/sdl_gui.jpg new file mode 100644 index 00000000..f6e3d484 Binary files /dev/null and b/pics/screenshots/sdl_gui.jpg differ diff --git a/pics/web/paraslash.ico b/pics/web/paraslash.ico new file mode 100644 index 00000000..a6b29dd9 Binary files /dev/null and b/pics/web/paraslash.ico differ diff --git a/pics/web/paraslash.png b/pics/web/paraslash.png new file mode 100644 index 00000000..d8082c68 Binary files /dev/null and b/pics/web/paraslash.png differ diff --git a/play.c b/play.c new file mode 100644 index 00000000..99c23169 --- /dev/null +++ b/play.c @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2005-2006 Andre Noll + * + * 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 , which is + * based on the vplay program by Michael Beck. + */ + +#define BUFFER_SIZE 1000 * 1000 +#define WAV_HEADER_LEN 44 +#include /* gettimeofday */ +#include "para.h" +#include "play.cmdline.h" +#include + +enum { E_BROKEN_CONF, /* Broken configuration for this PCM */ + E_ACCESS_TYPE, /* Access type not available */ + E_SAMPLE_FORMAT, /* Sample format not available */ + E_CHANNEL_COUNT, /* Channels count not available */ + E_HW_PARAMS, /* Unable to install hw params */ + E_SW_PARAMS, /* Unable to install sw params */ + E_BAD_PERIOD, /* Can't use period equal to buffer size */ + E_GET_XFER, /* Unable to obtain xfer align */ + E_SET_XFER, /* snd_pcm_sw_params_set_xfer_align failed */ + E_MEM, /* not enough memory */ + E_READ, /* read error */ + E_WRITE, /* write error */ + E_PIPE, /* write to pipe with other side closed */ + E_PCM_OPEN, /* unable to open pcm */ + E_SND_PCM_INFO, /* pcm info error */ + E_GET_BUFFER_TIME, /* snd_pcm_hw_params_get_buffer_time_max failed */ + E_SET_BUFFER_TIME, /* snd_pcm_hw_params_set_buffer_time_near failed */ + E_SET_RATE, /* snd_pcm_hw_params_set_rate_near failed */ + E_START_THRESHOLD, /* snd_pcm_sw_params_set_start_threshold failed */ + E_STOP_THRESHOLD, /* snd_pcm_sw_params_set_stop_threshold failed */ + E_LOG, /* snd_output_stdio_attach failed */ + E_SYNTAX /* could not parse start_time option */ +}; + +#define FORMAT SND_PCM_FORMAT_S16_LE + +#define EXIT(EXP) \ +do { if (EXP) \ + fprintf (stderr, "error: " #EXP "\n"); exit(EXP);} \ +while (0) + +static snd_pcm_t *handle; +static unsigned char *audiobuf; +static snd_pcm_uframes_t chunk_size; +static size_t bytes_per_frame; +static struct timeval *start_time; +static struct gengetopt_args_info conf; + +/* + * read_wav_header - read WAV_HEADER_LEN bytes from stdin to audio buffer + * + * Exit on errors and on eof before WAV_HEADER_LEN could be read. + */ +static void read_wav_header(void) +{ + ssize_t ret, count = 0; + + while (count < WAV_HEADER_LEN) { + ret = read(STDIN_FILENO, audiobuf + count, WAV_HEADER_LEN - count); + if (ret <= 0) + EXIT(E_READ); + count += ret; + } +} + +/* + * set_alsa_params - Prepare the PCM handle for writing + * + * Install PCM software and hardware configuration. Exit on errors. + */ +static void set_alsa_params(void) +{ + snd_pcm_hw_params_t *hwparams; + snd_pcm_sw_params_t *swparams; + snd_pcm_uframes_t buffer_size, xfer_align, start_threshold, + stop_threshold; + unsigned buffer_time = 0; + int err; + + snd_pcm_hw_params_alloca(&hwparams); + snd_pcm_sw_params_alloca(&swparams); + if (snd_pcm_hw_params_any(handle, hwparams) < 0) + EXIT(E_BROKEN_CONF); + if (snd_pcm_hw_params_set_access(handle, hwparams, + SND_PCM_ACCESS_RW_INTERLEAVED) < 0) + EXIT(E_ACCESS_TYPE); + if (snd_pcm_hw_params_set_format(handle, hwparams, FORMAT) < 0) + EXIT(E_SAMPLE_FORMAT); + if (snd_pcm_hw_params_set_channels(handle, hwparams, conf.channels_arg) < 0) + EXIT(E_CHANNEL_COUNT); + if (snd_pcm_hw_params_set_rate_near(handle, hwparams, (unsigned int*) &conf.sample_rate_arg, 0) < 0) + EXIT(E_SET_RATE); + err = snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time, 0); + if (err < 0 || !buffer_time) + EXIT(E_GET_BUFFER_TIME); + if (buffer_time > 500000) + buffer_time = 500000; + if (snd_pcm_hw_params_set_buffer_time_near(handle, hwparams, + &buffer_time, 0) < 0) + EXIT(E_SET_BUFFER_TIME); + if (snd_pcm_hw_params(handle, hwparams) < 0) + EXIT(E_HW_PARAMS); + snd_pcm_hw_params_get_period_size(hwparams, &chunk_size, 0); + snd_pcm_hw_params_get_buffer_size(hwparams, &buffer_size); + if (chunk_size == buffer_size) + EXIT(E_BAD_PERIOD); + snd_pcm_sw_params_current(handle, swparams); + err = snd_pcm_sw_params_get_xfer_align(swparams, &xfer_align); + if (err < 0 || !xfer_align) + EXIT(E_GET_XFER); + snd_pcm_sw_params_set_sleep_min(handle, swparams, 0); + snd_pcm_sw_params_set_avail_min(handle, swparams, chunk_size); + /* round to closest transfer boundary */ + start_threshold = (buffer_size / xfer_align) * xfer_align; + if (start_threshold < 1) + start_threshold = 1; + if (snd_pcm_sw_params_set_start_threshold(handle, swparams, + start_threshold) < 0) + EXIT(E_START_THRESHOLD); + stop_threshold = buffer_size; + if (snd_pcm_sw_params_set_stop_threshold(handle, swparams, + stop_threshold) < 0) + EXIT(E_STOP_THRESHOLD); + if (snd_pcm_sw_params_set_xfer_align(handle, swparams, xfer_align) < 0) + EXIT(E_SET_XFER); + if (snd_pcm_sw_params(handle, swparams) < 0) + EXIT(E_SW_PARAMS); + bytes_per_frame = snd_pcm_format_physical_width(FORMAT) * conf.channels_arg / 8; +} + +/* + * pcm_write - push out pcm frames + * @data: pointer do data to be written + * @count: number of frames + * + * Return value: Number of bytes written. Exit on errors. + */ +static snd_pcm_sframes_t pcm_write(u_char *data, size_t count) +{ + snd_pcm_sframes_t r, result = 0; +#if 0 + if (count < chunk_size) { + snd_pcm_format_set_silence(FORMAT, data + + count * bytes_per_frame, + (chunk_size - count) * conf.channels_arg); + count = chunk_size; + } +#endif + while (count > 0) { + /* write interleaved frames */ + r = snd_pcm_writei(handle, data, count); + if (r == -EAGAIN || (r >= 0 && r < count)) + snd_pcm_wait(handle, 1); + else if (r == -EPIPE) + snd_pcm_prepare(handle); + else if (r < 0) + EXIT(E_WRITE); + if (r > 0) { + result += r; + count -= r; + data += r * bytes_per_frame; + } + } + return result; +} + +/* + * start_time_in_future - check if current time is later than start_time + * @diff: pointer to write remaining time to + * + * If start_time was not given, or current time is later than given + * start_time, return 0. Otherwise, return 1 and write the time + * difference between current time and start_time to diff. diff may be + * NULL. + * + */ +static int start_time_in_future(struct timeval *diff) +{ + struct timeval now; + + if (!conf.start_time_given) + return 0; + gettimeofday(&now, NULL); + return tv_diff(start_time, &now, diff) > 0? 1 : 0; +} + +/* + * do_initial_delay - sleep until time given at command line + * + * This is called if the initial buffer is filled. It returns + * immediately if no start_time was given at the command line + * or if the given start time is in the past. + * + */ +static void do_initial_delay(void) +{ + struct timeval diff; + int ret; + + fprintf(stderr, "initial delay\n"); + if (!conf.start_time_given) + return; +again: + if (!start_time_in_future(&diff)) + return; + ret = select(1, NULL, NULL, NULL, &diff); + if (ret < 0 && errno == EINTR) + goto again; +} + + +/* + * play_pcm - play raw pcm data + * @l: number of bytes already loaded + * + * If start_time was given, prebuffer data until buffer is full or + * start_time is reached. In any case, do not start playing before + * start_time. + */ +static void play_pcm(size_t l) +{ + ssize_t r, w; + unsigned long written = 0; + size_t chunk_bytes; + + set_alsa_params(); + chunk_bytes = chunk_size * bytes_per_frame; + audiobuf = realloc(audiobuf, BUFFER_SIZE); +// fprintf(stderr, "loaded: %d, chunk_bytes: %d\n", l, chunk_bytes); + if (!audiobuf) + EXIT(E_MEM); + for (;;) { + for (;;) { + if (l >= chunk_bytes) { + if (written) + break; + if (!start_time) + break; + if (!start_time_in_future(NULL)) + break; + if (l > BUFFER_SIZE) { + do_initial_delay(); + break; + } + } +// fprintf(stderr, "l = %d, chunk_Bytes = %d\n", l, chunk_bytes); + r = read(STDIN_FILENO, audiobuf + l, BUFFER_SIZE -l); + if (r < 0) + EXIT(E_READ); + l += r; +// fprintf(stderr, "loaded: %d, r= %d\n", l, r); + if (!r) + goto out;; + } + w = MIN(chunk_bytes, l); +// fprintf(stderr, "play: writing %d\n", w); + r = (ssize_t) pcm_write(audiobuf, w / bytes_per_frame) * bytes_per_frame; +// fprintf(stderr, "wrote %d\n", r); + if (r < 0) + EXIT(E_WRITE); + written += r; + l -= r; + if (l) + memmove(audiobuf, audiobuf + r, l); +// fprintf(stderr, "written %lu, loaded : %d\n", written, l); + } +out: + snd_pcm_drain(handle); +} + +/* + * check_wave - test if audio buffer contains a valid wave header + * + * If not, return 0, otherwise, store number of channels and sample rate + * in struct conf and return WAV_HEADER_LEN. + */ +static size_t check_wave(void) +{ + unsigned char *a = audiobuf; + if (a[0] != 'R' || a[1] != 'I' || a[2] != 'F' || a[3] != 'F') + return WAV_HEADER_LEN; + conf.channels_arg = (unsigned) a[22]; + conf.sample_rate_arg = a[24] + (a[25] << 8) + (a[26] << 16) + (a[27] << 24); +// fprintf(stderr, "channels: %d, rate: %d\n", conf.channels_arg, +// conf.sample_rate_arg); + return 0; +} + +int main(int argc, char *argv[]) +{ + snd_pcm_info_t *info; + snd_output_t *log; + struct timeval tv; + int err; + + cmdline_parser(argc, argv, &conf); + if (conf.start_time_given) { + if (sscanf(conf.start_time_arg, "%lu:%lu", + &tv.tv_sec, &tv.tv_usec) != 2) + EXIT(E_SYNTAX); + start_time = &tv; + } +// fprintf(stderr, "argc=%d, argv[1]=%s\n",argc, argv[1]); + snd_pcm_info_alloca(&info); + if (snd_output_stdio_attach(&log, stderr, 0) < 0) + EXIT(E_LOG); + err = snd_pcm_open(&handle, "plug:swmix", + SND_PCM_STREAM_PLAYBACK, 0); + if (err < 0) + EXIT(E_PCM_OPEN); + if ((err = snd_pcm_info(handle, info)) < 0) + EXIT(E_SND_PCM_INFO); + audiobuf = malloc(WAV_HEADER_LEN); + read_wav_header(); + play_pcm(check_wave()); + snd_pcm_close(handle); + free(audiobuf); + snd_output_close(log); + snd_config_update_free_global(); + return EXIT_SUCCESS; +} diff --git a/play.ggo b/play.ggo new file mode 100644 index 00000000..75fb1bae --- /dev/null +++ b/play.ggo @@ -0,0 +1,5 @@ +section "general options" +option "start_time" t "start playback at given time which must be in a:b format where a denotes seconds and b denotes microseconds since the epoch" string typestr="timeval" no +option "device" d "set PCM device" string typestr="device" default="plug:swmix" no +option "channels" c "number of channels (only neccessary for raw audio)" int typestr="num" default="2" no +option "sample_rate" s "force given sample rate (only neccessary for raw audio)" int typestr="num" default="44100" no diff --git a/rc4.h b/rc4.h new file mode 100644 index 00000000..c290b48c --- /dev/null +++ b/rc4.h @@ -0,0 +1 @@ +#define RC4_KEY_LEN 16 diff --git a/recv.c b/recv.c new file mode 100644 index 00000000..aaccb06a --- /dev/null +++ b/recv.c @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2005-2006 Andre Noll + * + * 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:-h for help on \n"); + exit(EXIT_SUCCESS); + } + return check_receiver_arg(conf.receiver_arg, receiver_num); +} + +int main(int argc, char *argv[]) +{ + int ret, eof = 0, max, r_opened = 0, receiver_num; + struct timeval timeout; + struct receiver *r = NULL; + fd_set rfds, wfds; + struct receiver_node rn; + + memset(&rn, 0, sizeof(struct receiver_node)); + for (ret = 0; receivers[ret].name; ret++) + receivers[ret].init(&receivers[ret]); + ret = -E_RECV_SYNTAX; + rn.conf = parse_config(argc, argv, &receiver_num); + if (!rn.conf) { + PARA_EMERG_LOG("%s", "parse failed\n"); + goto out; + } + r = &receivers[receiver_num]; + rn.receiver = r; + ret = r->open(&rn); + if (ret < 0) + goto out; + r_opened = 1; +recv: + FD_ZERO(&rfds); + FD_ZERO(&wfds); + timeout.tv_sec = 0; + timeout.tv_usec = 1000 * 1000; + max = -1; + ret = r->pre_select(&rn, &rfds, &wfds, &timeout); + max = MAX(max, ret); + + PARA_DEBUG_LOG("timeout: %lums\n", tv2ms(&timeout)); + ret = select(max + 1, &rfds, &wfds, NULL, &timeout); + if (ret < 0) { + if (errno == EINTR || errno == EAGAIN) + goto recv; + ret = -E_RECV_SELECT; + goto out; + } + ret = r->post_select(&rn, ret, &rfds, &wfds); + if (ret < 0) + goto out; + if (!ret) + eof = 1; + if (!rn.loaded) { + if (eof) + goto out; + goto recv; + } + ret = write(STDOUT_FILENO, rn.buf, rn.loaded); + PARA_DEBUG_LOG("wrote %d/%d\n", ret, rn.loaded); + if (ret < 0) { + ret = -E_WRITE_STDOUT; + goto out; + } + if (ret != rn.loaded) { + PARA_INFO_LOG("short write %d/%d\n", ret, rn.loaded); + memmove(rn.buf, rn.buf + ret, rn.loaded - ret); + } + rn.loaded -= ret; + if (rn.loaded || !eof) + goto recv; +out: + if (r_opened) + r->close(&rn); + if (r) + r->shutdown(); + if (ret < 0) + PARA_NOTICE_LOG("%d: (%s)\n", ret, PARA_STRERROR(-ret)); + return ret; +} diff --git a/recv.ggo b/recv.ggo new file mode 100644 index 00000000..b6d5a77c --- /dev/null +++ b/recv.ggo @@ -0,0 +1,11 @@ +option "loglevel" l "set loglevel (0-6)" int typestr="level" default="4" no +option "list_receivers" L "print list of available receivers" flag off +option "receiver" r "Select receiver. + +If options for the selected receiver are given, they must +be separated by ':' instead of white space. Example: + + -r http:-i:www.paraslash.org:-p:8009 +" + +string typestr="receiver_spec" default="http" no diff --git a/recv.h b/recv.h new file mode 100644 index 00000000..3284c29a --- /dev/null +++ b/recv.h @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2005-2006 Andre Noll + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ + +/** \file recv.h receiver-relates structures and exported symbols of recv_common.c */ + +/** + * describes one instance of a receiver + */ +struct receiver_node { + /** points to the corresponding receiver */ + struct receiver *receiver; + /** the output buffer */ + char *buf; + /** the amount of bytes in \a buf */ + size_t loaded; + /** receiver-specific data */ + void *private_data; + /** set to 1 if end of file is reached */ + int eof; + /** pointer to the configuration data for this instance */ + void *conf; +}; + +/** + * describes one supported paraslash receiver + * + * \sa http_recv.c, ortp_recv.c + */ +struct receiver { +/** + * + * + * the name of the receiver + */ + const char *name; +/** + * + * + * the receiver init function + * + * It must fill in all other function pointers and is assumed to succeed. + * + * \sa http_recv_init ortp_recv_init + */ + void (*init)(struct receiver *r); +/** + * + * + * the command line parser of the receiver + * + * It should check whether the command line options given by \a argc and \a + * argv are valid. On success, it should return a pointer to the + * receiver-specific configuration data determined by \a argc and \a argv. + * Note that this might be called more than once with different values of + * \a argc and \a argv. + * + */ + void * (*parse_config)(int argc, char **argv); +/** + * + * + * open one instance of the receiver + * + * This should allocate the output buffer of \a rn. and may also perform any + * other work necessary for retrieving the stream according to the + * configuration stored in the \a conf member of \a rn which is guaranteed to + * point to valid configuration data (as previously obtained from the config + * parser). + * + * \sa receiver_node::conf, receiver_node::buf + */ + int (*open)(struct receiver_node *rn); +/** + * + * + * close one instance of the receiver + * + * It should free all resources associated with given receiver node that were + * allocated during the corresponding open call. + * + * \sa receiver_node + */ + void (*close)(struct receiver_node *rn); +/** + * + * + * deactivate the receiver + * + * Clean up what init has allocated. + */ + void (*shutdown)(void); +/** + * + * + * add file descriptors to fd_sets and compute timeout for select(2) + * + * The pre_select function gets called from the driving application before + * entering its select loop. The receiver may use this hook to add any file + * descriptors to \a rfds and \a wfds in order to check the result later in the + * post_select hook. + * + * \a timeout is a value-result parameter, initially containing the timeout for + * select() which was set by the application or by another receiver node. If + * the receiver wants its pre_select function to be called at some earlier time + * than the time determined by \a timeout, it may set \a timeout to an + * appropriate smaller value. However, it must never increase this timeout. + * + * This function must return the highest-numbered descriptor it wants to being + * checked, or -1 if no file descriptors should be checked for this run. + * + * \sa select(2), receiver_node:private_data, time.c + */ + int (*pre_select)(struct receiver_node *rn, fd_set *rfds, + fd_set *wfds, struct timeval *timeout); +/** + * + * + * evaluate the result from select() + * + * If the call to select() was succesful, this hook gets called. It should + * check all file descriptors which were added to any of the the fd sets during + * the previous call to pre_select. According to the result, it may then use + * any non-blocking I/O to establish a connection or to receive the audio data. + * + * A negative return value is interpreted as an error. + * + * \sa select(2), struct receiver + */ + int (*post_select)(struct receiver_node *rn, int select_ret, + fd_set *rfds, fd_set *wfds); +}; + + +/** \cond */ +extern void http_recv_init(struct receiver *r); +#define HTTP_RECEIVER {.name = "http", .init = http_recv_init}, + +#ifdef HAVE_ORTP +extern void ortp_recv_init(struct receiver *r); +#define ORTP_RECEIVER {.name = "ortp", .init = ortp_recv_init}, +#else +#define ORTP_RECEIVER +#endif + +void *check_receiver_arg(char *ra, int *receiver_num); + + +extern struct receiver receivers[]; +extern void (*crypt_function_recv)(unsigned long len, const unsigned char *indata, unsigned char *outdata); +extern void (*crypt_function_send)(unsigned long len, const unsigned char *indata, unsigned char *outdata); + +#define DEFINE_RECEIVER_ARRAY struct receiver receivers[] = { \ + HTTP_RECEIVER \ + ORTP_RECEIVER \ + {.name = NULL}}; + +/** \endcond */ diff --git a/recv_common.c b/recv_common.c new file mode 100644 index 00000000..3a09e7c0 --- /dev/null +++ b/recv_common.c @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2006 Andre Noll + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ + +/** \file recv_common.c common functions of para_recv and para_audiod */ + +#include "para.h" + +#include "recv.h" +#include "string.h" + +void (*crypt_function_recv)(unsigned long len, const unsigned char *indata, unsigned char *outdata) = NULL; +void (*crypt_function_send)(unsigned long len, const unsigned char *indata, unsigned char *outdata) = NULL; + +DEFINE_RECEIVER_ARRAY; +static void *parse_receiver_args(int receiver_num, char *options) +{ + struct receiver *r = &receivers[receiver_num]; + char **argv; + int argc, i; + void *conf; + + +// PARA_DEBUG_LOG("%s, options: %s\n", r->name, +// options? options : "(none)"); + if (options) { +// PARA_DEBUG_LOG("%s options: %s\n", name, options); + argc = split_args(options, &argv, ':'); +// PARA_DEBUG_LOG("argc = %d, argv[0]: %s\n", fn->argc, fn->argv[0]); + for (i = argc; i >= 0; i--) + argv[i + 1] = argv[i]; + argc += 2; + } else { + argc = 1; + argv = para_malloc(2 * sizeof(char*)); + argv[0] = NULL; + argv[1] = NULL; + } + conf = r->parse_config(argc, argv); + if (!conf) { + for (i = 0; i < argc; i++) + free(argv[i]); + free(argv); + return NULL; + } + return conf; +} + +void *check_receiver_arg(char *ra, int *receiver_num) +{ + int j; + +// PARA_DEBUG_LOG("checking %s\n", ra); + for (j = 0; receivers[j].name; j++) { + const char *name = receivers[j].name; + size_t len = strlen(name); + char c; + if (strlen(ra) < len) + continue; + if (strncmp(name, ra, len)) + continue; + c = ra[len]; + if (c && c != ':') + continue; + if (c && !receivers[j].parse_config) + return NULL; + *receiver_num = j; + return parse_receiver_args(j, c? ra + len + 1: NULL); + } + PARA_ERROR_LOG("%s", "receiver not found:"); + return NULL; +} diff --git a/ringbuffer.c b/ringbuffer.c new file mode 100644 index 00000000..b2ff0c7d --- /dev/null +++ b/ringbuffer.c @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2006 Andre Noll + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ + +/** \file ringbuffer.c simple ringbuffer implementation */ + +#include "gcc-compat.h" +#include "para.h" +#include "ringbuffer.h" +#include "string.h" + +/** + * holds all information about one ring buffer + * + * It is intentionally not exported via ringbuffer.h. Think abstract. + */ +struct ringbuffer +{ +/** + * + * + * the size of this ring buffer +*/ +unsigned size; +/** + * + * + * the actual entries of the ringbuffer +*/ +void **entries; +/** + * + * + * the next entry will be added at this position + */ +int head; +/** + * + * how many entries the ring buffer contains +*/ +unsigned filled; +}; + +/** + * initialize a new ringbuffer + * + * @param size the number of entries the ringbuffer holds + * + * This function initializes a circular ring buffer which can hold up to \a + * size entries of arbitrary type. If performance is an issue, \a size should + * be a power of two to make the underlying modulo operations cheap. Arbitrary + * many ringbuffers may be initialized via this function. Each ringbuffer is + * identified by a 'cookie'. + * + * Return value: A 'cookie' which identifies the ringbuffer just created and + * which must be passed to ringbuffer_add() and ringbuffer_get(). + */ +void *ringbuffer_new(unsigned size) +{ + struct ringbuffer *rb = para_calloc(sizeof(struct ringbuffer)); + rb->entries = para_calloc(size * sizeof(void *)); + rb->size = size; + return rb; +}; + +/** + * add one entry to a ringbuffer + * + * @param cookie the ringbuffer identifier + * @param data the data to be inserted + * + * insert \a data into the ringbuffer associated with \a cookie. As soon as + * the ringbuffer fills up, its oldest entry is disregarded and replaced by \a + * data. + * + * \return The old \a data pointer which is going to be disregarded, or + * NULL if the ringbuffer is not yet full. + */ +void *ringbuffer_add(void *cookie, void *data) +{ + struct ringbuffer *rb = cookie; + void *ret = rb->entries[rb->head]; + rb->entries[rb->head] = data; + rb->head = (rb->head + 1) % rb->size; + if (rb->filled < rb->size) + rb->filled++; + return ret; +} + +/** + * get one entry from a ringbuffer + * + * @param cookie the ringbuffer identifier + * @param num the number of the entry + * + * \return A pointer to data previously added, or NULL if entry number + * \a num is not available. \a num counts backwards from zero, i.e. + * ringbuffer_get_entry(0) gets the entry which was added most recently. + */ +void *ringbuffer_get(void *cookie, int num) +{ + struct ringbuffer *rb = cookie; + int pos = (rb->head + rb->size - 1 - num) % rb->size; +// fprintf(stderr, "pos = %d\n", pos); + return rb->entries[pos]; +} + +/** + * get the number of entries in the ring buffer + * + * @param cookie the ringbuffer identifier + * + * This function always succeeds and never returns a number greater than the + * size of the ring buffer. + */ +unsigned ringbuffer_filled(void *cookie) +{ + struct ringbuffer *rb = cookie; + return rb->filled; +} diff --git a/ringbuffer.h b/ringbuffer.h new file mode 100644 index 00000000..c7239347 --- /dev/null +++ b/ringbuffer.h @@ -0,0 +1,6 @@ +/** \file ringbuffer.h exported symbols from ringbuffer.c */ +void *ringbuffer_new(unsigned size); +void *ringbuffer_add(void *cookie, void *data); +void *ringbuffer_get(void *cookie, int num); +unsigned ringbuffer_filled(void *cookie); + diff --git a/scripts/demo-script b/scripts/demo-script new file mode 100755 index 00000000..003aa7b0 --- /dev/null +++ b/scripts/demo-script @@ -0,0 +1,56 @@ +#!/bin/bash + +bin="para_client para_audioc para_audiod para_gui" # the binaries we need +log="`pwd`/demo-log.$$.log" +dir="$HOME/.paraslash" +client_conf="$dir/client.conf" +audioc_conf="$dir/audioc.conf" +server=www.paraslash.org +proj=paraslash-0.2.10 +df=$proj.tar.bz2 # download file +url=http://$server/versions/$df +kf="$dir/key.anonymous" # key file +key_url=http://$server/key.anonymous +socket="$dir/socket" +receiver_opts="mp3:http:-i:$server:-p:8009" +audiod_log="$dir/audiod.log" +audiod_opts="-FDdr $receiver_opts -L $audiod_log -s $socket" +msg() +{ + echo "`date`: $1" +} + +go() +{ + msg "downloading $df" + wget -N -q $url || return 1 + msg "decompressing" + rm -rf $proj; tar xjf $df + cd $proj || return 1 + msg "configuring (be patient, ignore warnings but not errors)" + ./configure --prefix "$HOME" >> "$log" + msg "building $bin" + make $bin >> "$log" + msg "installing" + mkdir -p $HOME/bin && install -c -s -m 755 $bin $HOME/bin || return 1 + export PATH=$HOME/bin:$PATH + msg "retrieving anonymous key" + mkdir -p $dir || return 1 + wget -q --directory-prefix=$dir $key_url || return 1 + msg "writing $client_conf" + cat << EOF > "$client_conf" + user "anonymous" + hostname "$server" + key_file "$kf" +EOF + msg "writing $audioc_conf" + echo "socket \"$socket\"" > "$audioc_conf" + (para_audioc term; killall para_audiod para_client) >> "$log" 2>&1 + msg "para_audiod $audiod_opts" + para_audiod $audiod_opts -w "mp3:mpg123 -" + echo "hit return to start para_gui, hit ctrl+c to abort" + read + para_gui +} + +go diff --git a/sdl_gui.c b/sdl_gui.c new file mode 100644 index 00000000..bdc809b5 --- /dev/null +++ b/sdl_gui.c @@ -0,0 +1,840 @@ +/* + * Copyright (C) 2003-2006 Andre Noll + * + * 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 +#include "SFont.h" + +#include +#include +#include +#include +#include /* timeval, needed for select */ + +#include "sdl_gui.cmdline.h" + +#define FRAME_RED 100 +#define FRAME_GREEN 200 +#define FRAME_BLUE 200 + +SDL_Surface *screen; +static int width = 0; +static int height = 0; + +extern const char *status_item_list[NUM_STAT_ITEMS]; +struct gengetopt_args_info args_info; + +#define FRAME_WIDTH 10 +#define FONT_HEIGHT 36 + +#define INPUT_WIDTH width - 2 * FRAME_WIDTH +#define INPUT_X FRAME_WIDTH +#define INPUT_HEIGHT (args_info.interactive_flag? FONT_HEIGHT : 0) +#define INPUT_Y height - FRAME_WIDTH - INPUT_HEIGHT + +#define OUTPUT_WIDTH width - 2 * FRAME_WIDTH +#define OUTPUT_X FRAME_WIDTH +#define OUTPUT_Y FRAME_WIDTH +#define OUTPUT_HEIGHT (height - INPUT_HEIGHT - 2 * FRAME_WIDTH) + +#define NUM_LINES (height - 2 * FRAME_WIDTH - INPUT_HEIGHT) / FONT_HEIGHT + +#define M_YELLOW 0 +#define N_YELLOW 1 + +#define LEFT 1 +#define RIGHT 2 +#define CENTER 3 + + +struct stat_item{ + char *name; + char *prefix; + char *postfix; + char *content; + unsigned x; + unsigned y; + unsigned w; + unsigned h; + Uint8 r; + Uint8 g; + Uint8 b; + int font; + int align; +}; + +struct font { + char name[MAXLINE]; + SFont_FontInfo fontinfo; +}; + +struct font fonts[] = { + { + .name = "24P_Arial_Metallic_Yellow.png", + .fontinfo = {NULL, {0}, 0} + }, + { + .name = "24P_Arial_NeonYellow.png", + .fontinfo = {NULL, {0}, 0} + }, + { + .name = "24P_Copperplate_Blue.png", + .fontinfo = {NULL, {0}, 0} + }, + { + .name = "", + .fontinfo = {NULL, {0}, 0} + } +}; + + +#define PIC_WIDTH width * 3 / 10 +#define PIC_HEIGHT height / 3 + +#define INPUT_FONT 0 +#define OUTPUT_FONT 1 +#define MSG_FONT 2 + + +static struct stat_item stat_items[NUM_STAT_ITEMS]; + +void para_log(__unused int ll, __unused char* fmt,...) /* no logging */ +{ +} + +static void init_stat_items(void) +{ + int i; + struct stat_item *s = stat_items; + + for (i = 0; i < NUM_STAT_ITEMS; i++) { + s[i].w = 0; + s[i].content = NULL; + } + + + s[SI_STATUS_BAR].prefix = ""; + s[SI_STATUS_BAR].postfix = ""; + s[SI_STATUS_BAR].content = ""; + s[SI_STATUS_BAR].x = 0; + s[SI_STATUS_BAR].y = 10; + s[SI_STATUS_BAR].w = 100; + s[SI_STATUS_BAR].h = FONT_HEIGHT; + s[SI_STATUS_BAR].r = 0; + s[SI_STATUS_BAR].g = 0; + s[SI_STATUS_BAR].b = 0; + s[SI_STATUS_BAR].font = M_YELLOW; + s[SI_STATUS_BAR].align = CENTER; + + s[SI_PLAY_TIME].prefix = ""; + s[SI_PLAY_TIME].postfix = ""; + s[SI_PLAY_TIME].content = ""; + s[SI_PLAY_TIME].x = 35; + s[SI_PLAY_TIME].y = 20; + s[SI_PLAY_TIME].w = 65; + s[SI_PLAY_TIME].h = FONT_HEIGHT; + s[SI_PLAY_TIME].r = 0; + s[SI_PLAY_TIME].g = 0; + s[SI_PLAY_TIME].b = 0; + s[SI_PLAY_TIME].font = M_YELLOW; + s[SI_PLAY_TIME].align = CENTER; + + s[SI_STATUS].prefix = ""; + s[SI_STATUS].postfix = ""; + s[SI_STATUS].content = ""; + s[SI_STATUS].x = 35; + s[SI_STATUS].y = 28; + s[SI_STATUS].w = 12; + s[SI_STATUS].h = FONT_HEIGHT; + s[SI_STATUS].r = 0; + s[SI_STATUS].g = 0; + s[SI_STATUS].b = 0; + s[SI_STATUS].font = N_YELLOW; + s[SI_STATUS].align = LEFT; + + s[SI_STATUS_FLAGS].prefix = " ("; + s[SI_STATUS_FLAGS].postfix = ")"; + s[SI_STATUS_FLAGS].content = ""; + s[SI_STATUS_FLAGS].x = 47; + s[SI_STATUS_FLAGS].y = 28; + s[SI_STATUS_FLAGS].w = 15; + s[SI_STATUS_FLAGS].h = FONT_HEIGHT; + s[SI_STATUS_FLAGS].r = 0; + s[SI_STATUS_FLAGS].g = 0; + s[SI_STATUS_FLAGS].b = 0; + s[SI_STATUS_FLAGS].font = N_YELLOW; + s[SI_STATUS_FLAGS].align = CENTER; + + s[SI_NUM_PLAYED].prefix = "#"; + s[SI_NUM_PLAYED].postfix = ""; + s[SI_NUM_PLAYED].content = "0"; + s[SI_NUM_PLAYED].x = 62; + s[SI_NUM_PLAYED].y = 28; + s[SI_NUM_PLAYED].w = 13; + s[SI_NUM_PLAYED].h = FONT_HEIGHT; + s[SI_NUM_PLAYED].r = 0; + s[SI_NUM_PLAYED].g = 0; + s[SI_NUM_PLAYED].b = 0; + s[SI_NUM_PLAYED].font = N_YELLOW; + s[SI_NUM_PLAYED].align = CENTER; + + s[SI_UPTIME].prefix = "Up: "; + s[SI_UPTIME].postfix = ""; + s[SI_UPTIME].content = ""; + s[SI_UPTIME].x = 75; + s[SI_UPTIME].y = 28; + s[SI_UPTIME].w = 25; + s[SI_UPTIME].h = FONT_HEIGHT; + s[SI_UPTIME].r = 0; + s[SI_UPTIME].g = 0; + s[SI_UPTIME].b = 0; + s[SI_UPTIME].font = N_YELLOW; + s[SI_UPTIME].align = RIGHT; + + s[SI_DBTOOL].prefix = "dbtool: "; + s[SI_DBTOOL].postfix = ""; + s[SI_DBTOOL].content = "no content yet"; + s[SI_DBTOOL].x = 35; + s[SI_DBTOOL].y = 48; + s[SI_DBTOOL].w = 35; + s[SI_DBTOOL].h = FONT_HEIGHT; + s[SI_DBTOOL].r = 0; + s[SI_DBTOOL].g = 0; + s[SI_DBTOOL].b = 0; + s[SI_DBTOOL].font = N_YELLOW; + s[SI_DBTOOL].align = LEFT; + + s[SI_FORMAT].prefix = "Format: "; + s[SI_FORMAT].postfix = ""; + s[SI_FORMAT].content = ""; + s[SI_FORMAT].x = 70; + s[SI_FORMAT].y = 48; + s[SI_FORMAT].w = 30; + s[SI_FORMAT].h = FONT_HEIGHT; + s[SI_FORMAT].r = 0; + s[SI_FORMAT].g = 0; + s[SI_FORMAT].b = 0; + s[SI_FORMAT].font = N_YELLOW; + s[SI_FORMAT].align = RIGHT; + + s[SI_MTIME].prefix = "MTime: "; + s[SI_MTIME].postfix = ""; + s[SI_MTIME].content = ""; + s[SI_MTIME].x = 35; + s[SI_MTIME].y = 35; + s[SI_MTIME].w = 65; + s[SI_MTIME].h = FONT_HEIGHT; + s[SI_MTIME].r = 0; + s[SI_MTIME].g = 0; + s[SI_MTIME].b = 0; + s[SI_MTIME].font = N_YELLOW; + s[SI_MTIME].align = LEFT; + + s[SI_FILE_SIZE].prefix = "Size: "; + s[SI_FILE_SIZE].postfix = "kb"; + s[SI_FILE_SIZE].content = ""; + s[SI_FILE_SIZE].x = 35; + s[SI_FILE_SIZE].y = 42; + s[SI_FILE_SIZE].w = 20; + s[SI_FILE_SIZE].h = FONT_HEIGHT; + s[SI_FILE_SIZE].r = 0; + s[SI_FILE_SIZE].g = 0; + s[SI_FILE_SIZE].b = 0; + s[SI_FILE_SIZE].font = N_YELLOW; + s[SI_FILE_SIZE].align = LEFT; + + s[SI_AUDIO_INFO1].prefix = ""; + s[SI_AUDIO_INFO1].postfix = ""; + s[SI_AUDIO_INFO1].content = ""; + s[SI_AUDIO_INFO1].x = 0; + s[SI_AUDIO_INFO1].y = 60; + s[SI_AUDIO_INFO1].w = 100; + s[SI_AUDIO_INFO1].h = FONT_HEIGHT; + s[SI_AUDIO_INFO1].r = 0; + s[SI_AUDIO_INFO1].g = 0; + s[SI_AUDIO_INFO1].b = 0; + s[SI_AUDIO_INFO1].font = N_YELLOW; + s[SI_AUDIO_INFO1].align = CENTER; + + s[SI_AUDIO_INFO2].prefix = ""; + s[SI_AUDIO_INFO2].postfix = ""; + s[SI_AUDIO_INFO2].content = ""; + s[SI_AUDIO_INFO2].x = 0; + s[SI_AUDIO_INFO2].y = 65; + s[SI_AUDIO_INFO2].w = 100; + s[SI_AUDIO_INFO2].h = FONT_HEIGHT; + s[SI_AUDIO_INFO2].r = 0; + s[SI_AUDIO_INFO2].g = 0; + s[SI_AUDIO_INFO2].b = 0; + s[SI_AUDIO_INFO2].font = N_YELLOW; + s[SI_AUDIO_INFO2].align = CENTER; + + s[SI_AUDIO_INFO3].prefix = ""; + s[SI_AUDIO_INFO3].postfix = ""; + s[SI_AUDIO_INFO3].content = ""; + s[SI_AUDIO_INFO3].x = 0; + s[SI_AUDIO_INFO3].y = 70; + s[SI_AUDIO_INFO3].w = 100; + s[SI_AUDIO_INFO3].h = FONT_HEIGHT; + s[SI_AUDIO_INFO3].r = 0; + s[SI_AUDIO_INFO3].g = 0; + s[SI_AUDIO_INFO3].b = 0; + s[SI_AUDIO_INFO3].font = N_YELLOW; + s[SI_AUDIO_INFO3].align = CENTER; + + s[SI_DBINFO1].name = "dbinfo1:"; + s[SI_DBINFO1].prefix = ""; + s[SI_DBINFO1].postfix = ""; + s[SI_DBINFO1].content = ""; + s[SI_DBINFO1].x = 0; + s[SI_DBINFO1].y = 83; + s[SI_DBINFO1].w = 100; + s[SI_DBINFO1].h = FONT_HEIGHT; + s[SI_DBINFO1].r = 0; + s[SI_DBINFO1].g = 0; + s[SI_DBINFO1].b = 0; + s[SI_DBINFO1].font = N_YELLOW; + s[SI_DBINFO1].align = CENTER; + + s[SI_DBINFO2].prefix = ""; + s[SI_DBINFO2].postfix = ""; + s[SI_DBINFO2].content = ""; + s[SI_DBINFO2].x = 0; + s[SI_DBINFO2].y = 88; + s[SI_DBINFO2].w = 100; + s[SI_DBINFO2].h = FONT_HEIGHT; + s[SI_DBINFO2].r = 0; + s[SI_DBINFO2].g = 0; + s[SI_DBINFO2].b = 0; + s[SI_DBINFO2].font = N_YELLOW; + s[SI_DBINFO2].align = CENTER; + + s[SI_DBINFO3].name = "dbinfo3:"; + s[SI_DBINFO3].prefix = ""; + s[SI_DBINFO3].postfix = ""; + s[SI_DBINFO3].content = ""; + s[SI_DBINFO3].x = 0; + s[SI_DBINFO3].y = 93; + s[SI_DBINFO3].w = 100; + s[SI_DBINFO3].h = FONT_HEIGHT; + s[SI_DBINFO3].r = 0; + s[SI_DBINFO3].g = 0; + s[SI_DBINFO3].b = 0; + s[SI_DBINFO3].font = N_YELLOW; + s[SI_DBINFO3].align = CENTER; +} + +/* + * init SDL libary and set window title + */ +static void init_SDL(void) +{ + if (SDL_Init(SDL_INIT_VIDEO) == -1) { + fprintf(stderr, + "Couldn't initialize SDL: %s\n", SDL_GetError()); + exit(1); + } + /* Clean up on exit */ + atexit(SDL_Quit); + /* Initialize the display */ + if (args_info.fullscreen_flag) + screen = SDL_SetVideoMode(width, height, 0, SDL_FULLSCREEN); + else + screen = SDL_SetVideoMode(width, height, 0, 0); + if (!screen) { + fprintf(stderr, "Couldn't set video mode: %s\n", + SDL_GetError()); + exit(1); + } + SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE); + SDL_EventState(SDL_MOUSEBUTTONDOWN, SDL_IGNORE); + SDL_EventState(SDL_MOUSEBUTTONUP, SDL_IGNORE); + /* Set the window manager title bar */ + SDL_WM_SetCaption("The Gui of death that makes you blind (paraslash " + VERSION ")", "SFont"); +} + +/* + * draw rectangular frame of width FRAME_WIDTH + */ +static void draw_frame(Uint8 r, Uint8 g, Uint8 b) { + SDL_Rect rect; + + rect.x = 0; + rect.y = 0; + rect.w = width; + rect.h = FRAME_WIDTH; + SDL_FillRect(screen, &rect, SDL_MapRGB(screen->format, r, g, b)); + SDL_UpdateRect(screen, rect.x, rect.y, rect.w, rect.h); + + rect.x = 0; + rect.y = height - FRAME_WIDTH; + rect.w = width; + rect.h = FRAME_WIDTH; + SDL_FillRect(screen, &rect, SDL_MapRGB(screen->format, r, g, b)); + SDL_UpdateRect(screen, rect.x, rect.y, rect.w, rect.h); + + rect.x = 0; + rect.y = FRAME_WIDTH; + rect.w = FRAME_WIDTH; + rect.h = height - 2 * FRAME_WIDTH; + SDL_FillRect(screen, &rect, SDL_MapRGB(screen->format, r, g, b)); + SDL_UpdateRect(screen, rect.x, rect.y, rect.w, rect.h); + + rect.x = width - FRAME_WIDTH; + rect.y = FRAME_WIDTH; + rect.w = FRAME_WIDTH; + rect.h = height - 2 * FRAME_WIDTH; + SDL_FillRect(screen, &rect, SDL_MapRGB(screen->format, r, g, b)); + SDL_UpdateRect(screen, rect.x, rect.y, rect.w, rect.h); +} + +/* + * fill input rect with color + */ +static void fill_input_rect(void) +{ + SDL_Rect rect; + + rect.x = INPUT_X; + rect.y = INPUT_Y; + rect.w = INPUT_WIDTH; + rect.h = INPUT_HEIGHT; + SDL_FillRect(screen, &rect, SDL_MapRGB(screen->format, 10, 150, 10)); +} + +/* + * fill output rect with color + */ +static void fill_output_rect(void) +{ + SDL_Rect rect; + + rect.x = OUTPUT_X; + rect.y = OUTPUT_Y; + rect.w = OUTPUT_WIDTH; + rect.h = OUTPUT_HEIGHT; + SDL_FillRect(screen, &rect, SDL_MapRGB(screen->format, 0, 0, 0)); +} + +/* + * convert tab to space + */ +static void tab2space(char *text) +{ + char *p = text; + while (*p) { + if (*p == '\t') + *p = ' '; + p++; + } +} + +static void print_msg(char *msg) +{ + SFont_FontInfo *font = &(fonts[MSG_FONT].fontinfo); + char *buf = strdup(msg); + int len = strlen(buf); + + if (!buf) + return; + while (TextWidth2(font, buf) > INPUT_WIDTH && len > 0) { + *(buf + len) = '\0'; + len--; + } + fill_input_rect(); + PutString2(screen, font, INPUT_X, INPUT_Y, buf); + free(buf); +} + +static void update_all(void) +{ + SDL_UpdateRect(screen, 0, 0, 0, 0); +} + +static void update_input(void) +{ + SDL_UpdateRect(screen, INPUT_X, INPUT_Y, INPUT_WIDTH, INPUT_HEIGHT); + +} + +/* + * wait for key, ignore all other events, return 0 if there is no key event + * pending. Otherwise return keysym of key + */ +SDLKey get_key(void) +{ + SDL_Event event; + + while (SDL_PollEvent(&event) > 0) { + if(event.type != SDL_KEYDOWN) + continue; +// printf("Key pressed, scancode: 0x%x\n", +// event.key.keysym.scancode); + return event.key.keysym.sym; + } + return 0; +} + +/* + * print message, wait for key (blocking), return 1 for 'q', 0 else + */ +static SDLKey hit_key(char *msg) +{ + SDLKey sym; + + print_msg(msg); + update_input(); + while (!(sym = get_key())) + ; + fill_input_rect(); + update_input(); + if (sym == SDLK_q) + return 1; + else + return 0; +} + +/* + * read paraslash command from input, execute it and print results + */ +static int command_handler(void) +{ + FILE *pipe; + unsigned count = 0; + char text[MAXLINE]=""; + char buf[MAXLINE]=""; + SFont_FontInfo *font = &fonts[OUTPUT_FONT].fontinfo; + +// printf("string input\n"); + SFont_Input2(screen, &fonts[INPUT_FONT].fontinfo, + INPUT_X, INPUT_Y - 5, INPUT_WIDTH, text); + if (!strlen(text)) + return 1; + if (!strcmp(text, "exit") || !strcmp(text, "quit")) + return 0; + if (text[0] == '!') { + if (text[1] == '\0') + return 1; + pipe = popen(text + 1, "r"); + } else { + sprintf(buf, BINDIR "/para_client %s 2>&1", text); + pipe = popen(buf, "r"); + } + if (!pipe) + return 0; + fill_output_rect(); + while(fgets(text, MAXLINE - 1, pipe)) { + int len; + + tab2space(text); + len = strlen(text); + // printf("string: %s\n", dest); + while (TextWidth2(font, text) > width - 2 * FRAME_WIDTH && + len > 0) { + text[len] = '\0'; + len--; + } + PutString2(screen, font, OUTPUT_X, + OUTPUT_Y + count * FONT_HEIGHT, text); + count++; + if (count >= NUM_LINES) { + update_all(); + if (hit_key("Hit any key to continue, q to return")) + goto out; + count = 0; + fill_output_rect(); + } + } + update_all(); + hit_key("Hit any key to return"); +out: fill_output_rect(); + pclose(pipe); + return 1; +} + + +/* + * Add prefix and postfix to string, delete characters from the end + * if its length exceeds the max length defined in stat_items[item] + */ +char *transform_string(int item) +{ + struct stat_item s = stat_items[item]; + size_t len; + char *ret; + unsigned pixels = s.w * (width - 2 * FRAME_WIDTH) / 100; + SFont_FontInfo *font = &(fonts[s.font].fontinfo); + + ret = make_message("%s%s%s", s.prefix, s.content, s.postfix); + len = strlen(ret); + while (TextWidth2(font, ret) > pixels && len > 0) { + *(ret + len) = '\0'; + len--; + } + return ret; +} + +SDL_Surface *load_jpg(void) +{ + SDL_RWops *rwop; + int fds[3] = {0, 1, 0}; + pid_t pid; + FILE *pipe; + + if (para_exec_cmdline_pid(&pid, args_info.pic_cmd_arg, fds) < 0) + return NULL; + pipe = fdopen(fds[1], "r"); + if (!pipe) + return NULL; + if (!(rwop = SDL_RWFromFP(pipe, 0))) + return NULL; + return IMG_LoadJPG_RW(rwop); +} + +void update_pic(void) +{ + SDL_Surface *img; + SDL_Rect src_pic_rect = { + .x = 0, + .y = 0, + .w = PIC_WIDTH, + .h = PIC_HEIGHT, + }; + SDL_Rect dest_pic_rect = { + .x = FRAME_WIDTH, + .y = OUTPUT_HEIGHT / 5, + .w = PIC_WIDTH, + .h = PIC_HEIGHT, + }; + + if (!screen) + return; + + if (!(img = load_jpg())) + return; + SDL_FillRect(screen, &dest_pic_rect, SDL_MapRGB(screen->format, + 0, 0, 0)); + SDL_BlitSurface(img, &src_pic_rect, screen, &dest_pic_rect); + SDL_Flip(screen); + SDL_FreeSurface(img); +} + +/* + * update status item number i. + */ +static void do_update(int i) +{ + static int last_played = -1; + SDL_Rect rect; + char *buf; + SFont_FontInfo *font = &(fonts[stat_items[i].font].fontinfo); + if (!stat_items[i].w) + return; + + rect.x = stat_items[i].x * (width - FRAME_WIDTH * 2) / 100 + + FRAME_WIDTH; + rect.y = stat_items[i].y * (height - 2 * FRAME_WIDTH - INPUT_HEIGHT) + / 100; + rect.w = stat_items[i].w * (width - 2 * FRAME_WIDTH) / 100; + rect.h = stat_items[i].h; + buf = transform_string(i); + SDL_FillRect(screen, &rect, SDL_MapRGB(screen->format, + stat_items[i].r, stat_items[i].g, stat_items[i].b)); + switch(stat_items[i].align) { + case CENTER: + PutString2(screen, font, + rect.x + (rect.w - TextWidth2(font, buf)) / 2, + rect.y, buf); + break; + case LEFT: + PutString2(screen, font, rect.x, rect.y, buf); + break; + case RIGHT: + PutString2(screen, font, rect.x + (rect.w - + TextWidth2(font, buf)), rect.y, buf); + break; + } + free(buf); + SDL_UpdateRect(screen, rect.x, rect.y, rect.w, rect.h); + if (i == SI_NUM_PLAYED && atoi(stat_items[i].content) != last_played) { + update_pic(); + last_played = atoi(stat_items[i].content); + }; +} + +/* + * Check if buf is a known status line. If so call do_update and return 1. + * Return 0 otherwise. + */ +void update_status(char *buf) +{ + int i; + + i = stat_line_valid(buf); + if (i < 0) + return; + //free(stat_items[i].content); + stat_items[i].content = para_strdup(buf + + strlen(status_item_list[i]) + 1); + do_update(i); +} + +/* + * Read stat line from pipe if pipe is ready, call update_status to + * display information. + */ +static int draw_status(int pipe) +{ + fd_set rfds; + int ret; + struct timeval tv; + + tv.tv_sec = 0; + tv.tv_usec = 3000000; + FD_ZERO(&rfds); + FD_SET(pipe, &rfds); + ret = select(pipe + 1, &rfds, NULL, NULL, &tv); +// printf("select returned %d\n", ret); + if (ret <= 0) + return 0; + if (read_audiod_pipe(pipe, update_status) > 0) + return 1; +// clear_all_items(); + free(stat_items[SI_STATUS_BAR].content); + stat_items[SI_STATUS_BAR].content = + para_strdup("audiod not running!?\n"); + update_all(); + sleep(1); + return -1; +} + +static void clean_exit(int ret) +{ + SDL_Quit(); + exit(ret); +} + +static void print_help(void) +{ + print_msg("Hit q to quit, any other key to enter command mode"); +} + +static int configfile_exists(void) +{ + if (!args_info.config_file_given) { + char *home = para_homedir(); + args_info.config_file_arg = make_message( + "%s/.paraslash/sdl_gui.conf", home); + free(home); + } + return file_exists(args_info.config_file_arg); +} + +/* + * MAIN + */ +int main(int argc, char *argv[]) +{ + int i, ret, pipe; + SDLKey sym; + + cmdline_parser(argc, argv, &args_info); + ret = configfile_exists(); +// printf("w=%i,h=%i,ret=%i, cf=%s\n", width, height, ret, args_info.config_file_arg); + + if (!ret && args_info.config_file_given) { + fprintf(stderr, "Can't read config file %s\n", + args_info.config_file_arg); + exit(EXIT_FAILURE); + } + if (ret) + cmdline_parser_configfile(args_info.config_file_arg, + &args_info, 0, 0, 0); + signal(SIGCHLD, SIG_IGN); + width = args_info.width_arg; + height = args_info.height_arg; +// printf("w=%i,h=%i,ret=%i, cf=%s\n", width, height, ret, args_info.config_file_arg); + init_stat_items(); + pipe = para_open_audiod_pipe(args_info.stat_cmd_arg); + init_SDL(); + for (i = 0; fonts[i].name[0]; i++) { + char buf[MAXLINE]; + sprintf(buf, "%s/%s", FONTDIR, fonts[i].name); + /* Load the font - You don't have to use the IMGlib for this */ + fonts[i].fontinfo.Surface = IMG_Load(buf); + /* Prepare the font for use */ + InitFont2(&fonts[i].fontinfo); + } + draw_frame(FRAME_RED, FRAME_GREEN, FRAME_BLUE); + if (args_info.interactive_flag) { + print_help(); + update_input(); + } + for (;;) { + ret = draw_status(pipe); + if (ret < 0) { + close(pipe); + pipe = -1; + } + if (SDL_QuitRequested()) + clean_exit(0); + while ((sym = get_key())) { + if (!args_info.interactive_flag) + clean_exit(0); + if (sym == SDLK_q) + clean_exit(0); + if ( sym == SDLK_LSHIFT + || sym == SDLK_RSHIFT + || sym == SDLK_LMETA + || sym == SDLK_RMETA + || sym == SDLK_RCTRL + || sym == SDLK_LCTRL + || sym == SDLK_MODE + || sym == SDLK_CAPSLOCK + || sym == SDLK_LALT + || sym == SDLK_RALT + || sym == SDLK_RSUPER + || sym == SDLK_LSUPER + || sym == SDLK_COMPOSE + ) + continue; + if (pipe < 0) { +// printf("closing pipe\n"); + kill(0, SIGINT); + close(pipe); +// printf("pipe closed\n"); + } + fill_input_rect(); + update_input(); + if (!command_handler()) + clean_exit(0); + fill_output_rect(); + print_help(); + update_pic(); + SDL_UpdateRect(screen, 0, 0, 0, 0); + pipe = para_open_audiod_pipe(args_info.stat_cmd_arg); + break; + } + } +} diff --git a/sdl_gui.ggo b/sdl_gui.ggo new file mode 100644 index 00000000..a318658a --- /dev/null +++ b/sdl_gui.ggo @@ -0,0 +1,9 @@ +# file sample1.ggo +option "fullscreen" f "Use fullscreeen mode" flag off +option "interactive" i "Activate interactive mode" flag off +option "width" x "Specify screen width" int typestr="pixels" default="1024" no +option "height" y "Specify screen height" int typestr="pixels" default="768" no +option "config_file" c "(default='~/.paraslash/sdl_gui.conf')" string typestr="filename" no +option "window-id" w "(currently ignored)" string typestr="filename" no +option "stat_cmd" s "command to read server and audiod status data from" string typestr="command" default="para_audioc -t 100 stat" no +option "pic_cmd" p "command to read pic from" string typestr="command" default="para_client pic" no diff --git a/send.h b/send.h new file mode 100644 index 00000000..2c6c7274 --- /dev/null +++ b/send.h @@ -0,0 +1,86 @@ +/** \file send.h sender-related defines and structures */ +/** the sender subcommands */ +enum {SENDER_ADD, SENDER_DELETE, SENDER_ALLOW, SENDER_DENY, SENDER_ON, SENDER_OFF}; + +/** the number of sender subcommands */ +#define NUM_SENDER_CMDS (SENDER_OFF + 1) + +/** + * describes one supported sender of para_server + * + * \sa http_send.c ortp_send.c + */ +struct sender { +/** the name of the sender */ + const char *name; +/** + * the init function of this sender + * + * It must fill in all function pointers of \a s as well as the \a client_cmds + * array, see below. It should also do all neccessary preparations to init + * this sending facility, for example it could open a tcp port. + */ + void (*init)(struct sender *s); +/** \p SENDER_ON or \p SENDER_OFF */ + int status; +/** + * return the help text of this sender + * + * The result must be dynamically allocated and is freed by the caller. + */ + char* (*help)(void); +/** + * return current status info about this sender + * + * The result must be dynamically allocated and is freed by the caller. + */ + char* (*info)(void); +/** + * the send-hook + * + * It gets called whenever para_server is playing and the current + * audio format handler indicates that another chunk of data should + * be sent now. The two parameters \a current_chunk and \a chunks_sent + * only differ if the stream was repositioned by the \a ff or \a jmp + * command. Of course, \a buf is a pointer to the chunk of data which + * should be sent, and \a len is the length of this buffer. +*/ + void (*send)(struct audio_format *af, long unsigned current_chunk, + long unsigned chunks_sent, const char *buf, size_t len); +/** add file descriptors to fd_sets + * + * The pre_select function of each supported sender is called just before + * para_server enters its main select loop. Each sender may add its own + * file descriptors to the \a rfds or the \a wfds set. + * + * If a file descriptor was added, \a max_fileno must be increased by + * this function, if neccessary. + * + * \sa select(2) +*/ + void (*pre_select)(struct audio_format *af, int *max_fileno, fd_set *rfds, + fd_set *wfds); +/** + * handle the file descriptors which are ready for I/O + * + * If the pre_select hook added one ore more file descriptors to the read or write + * set, this is the hook to check the result and do any I/O on those descriptors + * which are ready for reading/writing. + */ + void (*post_select)(struct audio_format *af, fd_set *rfds, fd_set *wfds); +/** + * terminate all connected clients + * + * This is called e.g. if the stop command was executed. It should make the clients + * aware of the end-of-file condition. + */ + void (*shutdown_clients)(void); +/** + * array of function pointers for the sender subcommands + * + * Each sender may implement any subset of the sender commands by filling in + * the aprropriate function pointer in the array. A \p NULL pointer means this + * command is not implemented by this sender. + */ + int (*client_cmds[NUM_SENDER_CMDS])(struct sender_command_data*); +}; diff --git a/server.c b/server.c new file mode 100644 index 00000000..71ee9eb6 --- /dev/null +++ b/server.c @@ -0,0 +1,583 @@ +/* + * Copyright (C) 1997-2006 Andre Noll + * + * 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 /* MAP_SHARED, PROT_READ, PROT_WRITE */ +#include "send.h" +#include "error.h" +#include "net.h" +#include "daemon.h" +#include "string.h" + +/** define the array of error lists needed by para_server */ +INIT_SERVER_ERRLISTS; + +/** shut down non-authorized connections after that many seconds */ +#define ALARM_TIMEOUT 10 + +/* these are exported to afs/command/dbtool */ +struct misc_meta_data *mmd; +/** the configuration of para_server + * + * It also contains the options for all database tools and all supported + * senders. +*/ +struct gengetopt_args_info conf; +char *user_list = NULL; +extern void http_send_init(struct sender *); +extern void ortp_send_init(struct sender *); +extern struct audio_format afl[]; + +/** the list of supported database tools */ +struct dbtool dblist[] = { + { + .name = "dopey", + .init = dopey_dbtool_init, + .update_audio_file = NULL, + }, +#ifdef HAVE_MYSQL + { + .name = "mysql", + .init = mysql_dbtool_init, + .update_audio_file = NULL, + }, +#endif + { + .name = NULL, + } +}; + +/** the list of supported senders */ +struct sender senders[] = { + { + .name = "http", + .init = http_send_init, + }, +#ifdef HAVE_ORTP + { + .name = "ortp", + .init = ortp_send_init, + }, +#endif + { + .name = NULL, + } +}; + + +/* global variables for server-internal use */ +static FILE *logfile; +static int mmd_semid; +static int signal_pipe; + +/** + * para_server's log function + * + * \param ll the log level + * \param fmt the format string describing the log message + */ +void para_log(int ll, char* fmt,...) +{ + va_list argp; + FILE *outfd; + struct tm *tm; + time_t t1; + char str[MAXLINE] = ""; + pid_t mypid; + + if (ll < conf.loglevel_arg) + return; + if (!logfile) { + if (ll < WARNING) + outfd = stdout; + else + outfd = stderr; + } else + outfd = logfile; + if (conf.daemon_given && !logfile) + return; + time(&t1); + tm = localtime(&t1); + strftime(str, MAXLINE, "%b %d %H:%M:%S", tm); + fprintf(outfd, "%s ", str); + if (conf.loglevel_arg <= INFO) + fprintf(outfd, "%i: ", ll); + mypid = getpid(); + if (conf.loglevel_arg <= INFO) + fprintf(outfd, "(%d) ", mypid); + va_start(argp, fmt); + vfprintf(outfd, fmt, argp); + va_end(argp); +} + + +/* + * setup shared memory area and get semaphore for locking + */ +static void shm_init(void) +{ + int fd; + caddr_t area; + + if ((fd = open("/dev/zero", O_RDWR)) < 0) { + PARA_EMERG_LOG("%s", "failed to open /dev/zero\n"); + exit(EXIT_FAILURE); + } + if ((area = mmap(0, sizeof(struct misc_meta_data), + PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0)) == (caddr_t) - 1) { + PARA_EMERG_LOG("%s", "mmap error\n"); + exit(EXIT_FAILURE); + } + close(fd); /* we dont need /dev/zero anymore */ + mmd = (struct misc_meta_data *)area; + + mmd->dbt_num = 0; + mmd->num_played = 0; + mmd->num_commands = 0; + mmd->events = 0; + mmd->num_connects = 0; + mmd->active_connections = 0; + strcpy(mmd->filename, "(none)"); + mmd->audio_format = -1; + mmd->afs_status_flags = AFS_NEXT; + mmd->new_afs_status_flags = AFS_NEXT; + mmd->sender_cmd_data.cmd_num = -1; + + mmd_semid = semget(42, 1, IPC_CREAT | 0666); + if (mmd_semid == -1) { + PARA_EMERG_LOG("%s", "semget failed\n"); + exit(EXIT_FAILURE); + } +} + +static void do_semop(struct sembuf *sops, int num) +{ + if (semop(mmd_semid, sops, num) >= 0) + return; + PARA_WARNING_LOG("semop failed (%s), retrying\n", strerror(errno)); + while (semop(mmd_semid, sops, num) < 0) + ; /* nothing */ +} + +/** + * lock the shared memory area containing the mmd struct + * + * \sa semop(2), struct misc_meta_data + */ +void mmd_lock(void) +{ + struct sembuf sops[2] = { + { + .sem_num = 0, + .sem_op = 0, + .sem_flg = SEM_UNDO + }, + { + .sem_num = 0, + .sem_op = 1, + .sem_flg = SEM_UNDO + } + }; + do_semop(sops, 2); +} + +/** + * unlock the shared memory area containing the mmd struct + * + * \sa semop(2), struct misc_meta_data + */ +void mmd_unlock(void) +{ + struct sembuf sops[1] = { + { + .sem_num = 0, + .sem_op = -1, + .sem_flg = SEM_UNDO + }, + }; + do_semop(sops, 1); +} + +static void parse_config(int override) +{ + char *home = para_homedir(); + struct stat statbuf; + int ret; + char *cf; + + if (conf.config_file_given) + cf = conf.config_file_arg; + else + cf = make_message("%s/.paraslash/server.conf", home); + free(user_list); + if (!conf.user_list_given) + user_list = make_message("%s/.paraslash/server.users", home); + else + user_list = para_strdup(conf.user_list_arg); + ret = stat(cf, &statbuf); + if (ret && conf.config_file_given) { + ret = -1; + PARA_EMERG_LOG("can not stat config file %s\n", cf); + goto out; + } + if (!ret) { + int tmp = conf.daemon_given; + cmdline_parser_configfile(cf, &conf, override, 0, 0); + conf.daemon_given = tmp; + } + /* logfile */ + if (!conf.logfile_given && conf.daemon_given) { + ret = -1; + PARA_EMERG_LOG("%s", "daemon, but no log file\n"); + goto out; + } + if (conf.logfile_given) + logfile = open_log(conf.logfile_arg); + ret = 1; +out: + free(cf); + free(home); + if (ret > 0) + return; + free(user_list); + user_list = NULL; + exit(EXIT_FAILURE); +} + +static void setup_signal_handling(void) +{ + int ret = 0; + + signal_pipe = para_signal_init(); +// fcntl(signal_pipe, F_SETFL, O_NONBLOCK); + PARA_NOTICE_LOG("%s", "setting up signal handlers\n"); + ret += para_install_sighandler(SIGINT); + ret += para_install_sighandler(SIGTERM); + ret += para_install_sighandler(SIGHUP); + ret += para_install_sighandler(SIGCHLD); + signal(SIGPIPE, SIG_IGN); + signal(SIGUSR1, SIG_IGN); + if (ret != 4) { + PARA_EMERG_LOG("%s", "could not install signal handlers\n"); + exit(EXIT_FAILURE); + } +} + +static void init_dbtool(void) +{ + int i; + + mmd->dbt_change = -1; /* no change nec., set to new dbt num by com_cdt */ + if (!dblist[1].name) + goto dopey; + if (conf.dbtool_given) { + for (i = 0; dblist[i].name; i++) { + if (strcmp(dblist[i].name, conf.dbtool_arg)) + continue; + PARA_NOTICE_LOG("initializing %s database tool\n", + dblist[i].name); + if (dblist[i].init(&dblist[i]) < 0) { + PARA_WARNING_LOG("init %s failed", + dblist[i].name); + goto dopey; + } + mmd->dbt_num = i; + return; + } + PARA_WARNING_LOG("%s", "no such dbtool, switching to dopey\n"); + goto dopey; + } + /* use the first dbtool that works + * (assuming that dopey always works) + */ + for (i = 1; dblist[i].name; i++) { + int ret = dblist[i].init(&dblist[i]); + if (ret >= 0) { + PARA_INFO_LOG("initialized %s\n", dblist[i].name); + mmd->dbt_num = i; + return; + } + PARA_CRIT_LOG("%s init failed: %s\n", dblist[i].name, + PARA_STRERROR(-ret)); + } +dopey: + mmd->dbt_num = 0; + dblist[0].init(&dblist[0]); /* always successful */ +} + +static unsigned init_network(void) +{ + int sockfd = init_tcp_socket(conf.port_arg); + + if (sockfd < 0) + exit(EXIT_FAILURE); + return sockfd; +} + +static void init_random_seed(void) +{ + int fd, ret = -1, len = sizeof(unsigned int); + unsigned int seed; + + fd = open("/dev/random", O_RDONLY); + if (fd < 0) + goto out; + ret = -2; + if (read(fd, &seed, len) != len) + goto out; + srandom(seed); + ret = 1; +out: + if (fd >= 0) + close(fd); + if (ret > 0) + return; + PARA_EMERG_LOG("can not seed pseudo random generator (ret = %d)\n", + ret); + exit(EXIT_FAILURE); +} + +static unsigned do_inits(int argc, char **argv) +{ + /* connector's address information */ + int sockfd; + + init_random_seed(); + /* parse command line options */ + cmdline_parser(argc, argv, &conf); + para_drop_privileges(conf.user_arg); + /* parse config file, open log and set defaults */ + parse_config(0); + log_welcome("para_server", conf.loglevel_arg); + shm_init(); /* init mmd struct */ + server_uptime(UPTIME_SET); /* reset server uptime */ + /* become daemon */ + if (conf.daemon_given) + daemon_init(); + init_dbtool(); + PARA_NOTICE_LOG("%s", "initializing audio file sender\n"); + /* audio file sender */ + afs_init(); + mmd->server_pid = getpid(); + setup_signal_handling(); + mmd_lock(); + /* init network socket */ + PARA_NOTICE_LOG("%s", "initializing tcp command socket\n"); + sockfd = init_network(); + if (conf.autoplay_given) { + mmd->afs_status_flags |= AFS_PLAYING; + mmd->new_afs_status_flags |= AFS_PLAYING; + } + PARA_NOTICE_LOG("%s", "init complete\n"); + return sockfd; +} + +static void handle_dbt_change(void) +{ + int ret, old = mmd->dbt_num, new = mmd->dbt_change; + + dblist[old].shutdown(); + ret = dblist[new].init(&dblist[new]); + mmd->dbt_change = -1; /* reset */ + if (ret >= 0) { + mmd->dbt_num = new; + return; + } + /* init failed */ + PARA_ERROR_LOG("%s -- switching to dopey\n", PARA_STRERROR(-ret)); + dblist[0].init(&dblist[0]); + mmd->dbt_num = 0; +} + +/* + * called when server gets SIGHUP or when client invokes hup command. + */ +static void handle_sighup(void) +{ + PARA_NOTICE_LOG("%s", "SIGHUP\n"); + close_log(logfile); /* gets reopened if necessary by parse_config */ + logfile = NULL; + parse_config(1); /* reopens log */ + mmd->dbt_change = mmd->dbt_num; /* do not change dbtool */ + handle_dbt_change(); /* force reloading dbtool */ +} + +static void status_refresh(void) +{ + static int prev_uptime = -1, prev_events = -1; + int uptime = server_uptime(UPTIME_GET), ret = 1; + + if (prev_events != mmd->events) + goto out; + if (mmd->new_afs_status_flags != mmd->afs_status_flags) + goto out; + if (uptime / 60 != prev_uptime / 60) + goto out; + ret = 0; +out: + prev_uptime = uptime; + prev_events = mmd->events; + mmd->afs_status_flags = mmd->new_afs_status_flags; + if (ret) { + PARA_DEBUG_LOG("%d events, forcing status update, af = %d\n", + mmd->events, mmd->audio_format); + killpg(0, SIGUSR1); + } +} + +/* + * MAIN + */ +int main(int argc, char *argv[]) +{ + /* listen on sock_fd, new connection on new_fd */ + int sockfd, new_fd; + struct sockaddr_in their_addr; + int err, i, max_fileno, ret; + pid_t chld_pid; + fd_set rfds, wfds; + struct timeval *timeout; + + valid_fd_012(); + sockfd = do_inits(argc, argv); +repeat: + /* check socket and signal pipe in any case */ + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_SET(sockfd, &rfds); + max_fileno = sockfd; + FD_SET(signal_pipe, &rfds); + max_fileno = MAX(max_fileno, signal_pipe); + + timeout = afs_preselect(); + status_refresh(); + for (i = 0; senders[i].name; i++) { + if (senders[i].status != SENDER_ON) + continue; + if (!senders[i].pre_select) + continue; + senders[i].pre_select(mmd->audio_format >= 0? + &afl[mmd->audio_format] : NULL, + &max_fileno, + &rfds, &wfds); + } + mmd_unlock(); +// PARA_DEBUG_LOG("%s: select (max = %i)\n", __func__, max_fileno); + ret = select(max_fileno + 1, &rfds, &wfds, NULL, timeout); + err = errno; + //PARA_DEBUG_LOG("%s: select returned %i\n", __func__, ret); + mmd_lock(); + if (mmd->dbt_change >= 0) + handle_dbt_change(); + if (ret < 0 && err == EINTR) + goto repeat; + if (ret < 0) { + PARA_CRIT_LOG("select error (%s)\n", strerror(err)); + goto repeat; + } + for (i = 0; senders[i].name; i++) { + if (senders[i].status != SENDER_ON) + continue; + if (!senders[i].post_select) + continue; + senders[i].post_select(mmd->audio_format >= 0? + &afl[mmd->audio_format] : NULL, + &rfds, &wfds); + } + if (!ret) { + afs_send_chunk(); + status_refresh(); + } + if (FD_ISSET(signal_pipe, &rfds)) { + int sig; + sig = para_next_signal(); + switch (sig) { + case SIGHUP: + handle_sighup(); + break; + case SIGCHLD: + para_reap_children(); + break; + /* die on sigint/sigterm. Kill all children too. */ + case SIGINT: + case SIGTERM: + PARA_EMERG_LOG("terminating on signal %d\n", sig); + kill(0, SIGTERM); + exit(EXIT_FAILURE); + } + } + if (mmd->sender_cmd_data.cmd_num >= 0) { + int num = mmd->sender_cmd_data.cmd_num, + s = mmd->sender_cmd_data.sender_num; + + if (senders[s].client_cmds[num]) + senders[s].client_cmds[num](&mmd->sender_cmd_data); + mmd->sender_cmd_data.cmd_num = -1; + } + if (!FD_ISSET(sockfd, &rfds)) + goto repeat; + + new_fd = para_accept(sockfd, &their_addr, sizeof(struct sockaddr_in)); + if (new_fd < 0) + goto repeat; + PARA_INFO_LOG("got connection from %s, forking\n", + inet_ntoa(their_addr.sin_addr)); + mmd->num_connects++; + mmd->active_connections++; + random(); + chld_pid = fork(); + if (chld_pid < 0) { + PARA_CRIT_LOG("%s", "fork failed\n"); + goto repeat; + } + if (chld_pid) { + close(new_fd); + /* parent keeps accepting connections */ + goto repeat; + } + alarm(ALARM_TIMEOUT); + close_listed_fds(); + close(sockfd); /* child doesn't need the listener */ + /* + * put info on who we are serving into argv[0] to make + * client ip visible in top/ps + */ + for (i = argc - 1; i >= 0; i--) + memset(argv[i], 0, strlen(argv[i])); + sprintf(argv[0], "para_server (serving %s)", + inet_ntoa(their_addr.sin_addr)); + return handle_connect(new_fd, &their_addr); +} diff --git a/server.ggo b/server.ggo new file mode 100644 index 00000000..52dc90b6 --- /dev/null +++ b/server.ggo @@ -0,0 +1,43 @@ +section "General options" +option "loglevel" l "set loglevel (0-6)" int typestr="level" default="4" no +option "port" p "port to listen on" int typestr="portnumber" default="2990" no +option "daemon" d "run as background daemon" flag off +option "user" u "run as user 'name'. para_server does not need any special privileges. If started as root (EUID == 0) this option must be given at the command line (not in the configuration file) so that para_server can drop the root privileges right after parsing the command line options, but before parsing the configuration file. In this case, real/effective/saved UID are all set to the UID of 'username'. As the configuration file is read afterwards, those options that have a default value depending on the UID (e.g. the home directory for the configuration file) are computed by using the uid of 'username'. This option has no effect if para_server ist started as a non-root user (i.e. EUID != 0)" string typestr="name" no + +section "Configuration files" +option "logfile" L "(default=stdout/stderr)" string typestr="filename" no +option "config_file" c "(default='~/.paraslash/server.conf'" string typestr="filename" no +option "user_list" - "(default='~/.paraslash/server.users')" string typestr="filename" no + +section "Options concerning the audio file sender" +option "autoplay" a "start playing on startup" flag off +option "announce_time" A "Delay betweeen announcing the stream and sending data" int typestr="milliseconds" default="300" no +option "dbtool" D "(default=first available that works)" string typestr="name_of_dbtool" no + +section "Mysql database tool options" +option "mysql_host" - "mysql server" string default="localhost" no +option "mysql_port" - "where mysql is listening" int default="3306" no +option "mysql_user" - "default value: username from /etc/passwd" string no +option "mysql_passwd" - "(required)" string no +option "mysql_database" - "name of mysql database" string default="paraslash" no +option "mysql_audio_file_dir" - "dir to search for audio files (required)" string no +option "mysql_default_score" - "scoring rule to use if stream definition does not contain explicit score definition" string default="(LASTPLAYED() / 1440 - 1000 / (LASTPLAYED() + 1) - sqrt(NUMPLAYED()))" no + + + +section "Dopey database tool options" +option "dopey_dir" - "dir to search for files to be streamed" string default="/home/music" no + +section "Http sender options" +option "http_port" - "tcp port for http streaming" int typestr="portnumber" default="8000" no +option "http_default_deny" - "deny connections from hosts which are not explicitly allowed" flag off +option "http_access" - "Add given host/network to access control list (whitelist if http_default_deny was given, blacklist otherwise) before opening the tcp port. This option can be given multiple times. Example: '192.168.0.0/24' whitelists/blacklists the 256 hosts 192.168.0.x" string typestr="a.b.c.d/n" no multiple +option "http_no_autostart" - "do not open tcp port on server startup" flag off +option "http_max_clients" - "maximal simultaneous connections, non-positive value means unlimited" int typestr="number" default="-1" no + +section "Ortp sender options" +option "ortp_target" - "Add given host/port to the list of targets. This option can be given multiple times. Example: '224.0.1.38:1500' instructs the ortp sender to send to udp port 1500 on host 224.0.1.38 (unassigned ip in the Local Network Control Block 224.0.0/24). This is useful for LAN-streaming." string typestr="a.b.c.d:p" no multiple +option "ortp_no_autostart" - "do not start to send automatically" flag off +option "ortp_default_port" - "default udp port if not specified" int typestr="portnumber" default="1500" no +option "ortp_header_interval" H "time between extra header sends" int typestr="milliseconds" default="2000" no + diff --git a/server.h b/server.h new file mode 100644 index 00000000..b01c3f6a --- /dev/null +++ b/server.h @@ -0,0 +1,155 @@ +/* + * Copyright (C) 1997-2006 Andre Noll + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ + +/** \file server.h common server data structures */ + +#include "para.h" + + +/** size of the dbinfo and audio_file info strings of struct misc_meta_data */ +#define MMD_INFO_SIZE 16384 + +/** + * permission flags that can be set individually for any server command + * + * - DB_READ: command reads from the database + * - DB_WRITE: command changes the contents of the database + * - AFS_READ: command reads information about the current audio stream + * - AFS_WRITE: command changes the current audio stream + */ +enum {DB_READ = 1, DB_WRITE = 2, AFS_READ = 4, AFS_WRITE = 8}; + +/** + * data needed to authenticate the user + */ +struct user{ +/** the username */ + char name[MAXLINE]; +/** full path to the public RSA key */ + char pubkey_file[_POSIX_PATH_MAX]; +/** the privileges of this user */ + unsigned int perms; +}; + +/** + * defines one command of para_server + */ +struct server_command { +/** the name of the command */ + char *name; +/** pointer to the function that handles the command */ + int (*handler)(int, int, char **); +/** the privileges a user must have to execute this command */ + unsigned int perms; +/** one-line description of the command */ + char *description; +/** summary of the command line options */ + char *synopsis; +/** the long help text */ + char *help; +}; + +/** holds the arguments for the para_server's sender command */ +struct sender_command_data{ +/** greater than 0 indicates that a sender cmd is already queued */ + int cmd_num; +/** the number of the sender in question */ + int sender_num; +/** used for the allow/deny/add/remove subcommands */ + struct in_addr addr; +/** used for allow/deny */ + int netmask; +/** the portnumber for add/remove */ + int port; +}; + +/** + * used for parent-child communication + * + * There's only one struct of this type which lives in shared memory + * for communication between the server instances. Access to this + * area is serialized via mmd_lock() and mmd_unlock(). There are two + * reasons for a variable to be included here: + * + * - At least one command (i.e. child of the server) must be able to + * change its value. + * + * or + * + * - The contents are listed in the stat command and have to be up to + * date. + */ +struct misc_meta_data{ +/** the size of the current audio file in bytes */ + long unsigned int size; +/** the full path of the current audio file */ + char filename[_POSIX_PATH_MAX]; +/** the last modification file of the current audio file */ + time_t mtime; +/* the number of the current audio format */ + int audio_format; +/** the "old" status flags -- commands may only read them */ + unsigned int afs_status_flags; +/** the new status flags -- commands may set them **/ + unsigned int new_afs_status_flags; +/** the number of data chunks sent for the current audio file */ + long unsigned chunks_sent; +/** the number of chunks this audio file contains */ + long unsigned chunks_total; +/** set by the jmp/ff commands to the new position in chunks */ + long unsigned repos_request; +/** the number of the chunk currently sent out*/ + long unsigned current_chunk; +/** the milliseconds that have been skipped of the current audio file */ + long offset; +/** the length of the audio file in seconds */ + int seconds_total; +/** the time para_server started to stream */ + struct timeval stream_start; +/** a string that gets filled in by the audio format handler */ + char audio_file_info[MMD_INFO_SIZE]; +/** the event counter + * + * commands may increase this to force a status update to be sent to all + * connected clients +*/ + unsigned int events; +/** the number of audio files already sent */ + unsigned int num_played; +/** the number of executed commands */ + unsigned int num_commands; +/** the number of connections para_server received so far */ + unsigned int num_connects; +/** the number of connections currently active */ + unsigned int active_connections; +/** the process id of para_server */ + pid_t server_pid; +/** a string that gets filled in by the current database tool */ + char dbinfo[MMD_INFO_SIZE]; +/** the number if the current database tool */ + int dbt_num; +/** commands set this to non-zero to request a database tool change */ + int dbt_change; +/* used by the sender command */ + struct sender_command_data sender_cmd_data; +}; + + +int handle_connect(int fd, struct sockaddr_in *addr); +void mmd_unlock(void); +void mmd_lock(void); diff --git a/signal.c b/signal.c new file mode 100644 index 00000000..82444cc8 --- /dev/null +++ b/signal.c @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2004-2006 Andre Noll + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +/** \file signal.c signal handling functions */ + +#include "para.h" +#include "error.h" +static int signal_pipe[2]; + +/** + * initialize the paraslash signal subsystem + * + * This function creates a pipe, the signal pipe, to deliver pending signals to + * the application. It should be called during the application's startup part, + * followed by subsequent calls to para_install_sighandler() for each signal + * that should be caught. + * + * para_signal_init() installs a generic signal handler which is used for all + * signals simultaneously. When a signal arrives, this generic signal handler + * writes the corresponding signal number to the signal pipe so that the + * application can test for pending signals simply by checking the signal pipe + * for reading, e.g. by using the select(2) system call. + * + * \return This function either succeeds or calls exit(2) to terminate + * the current process. On success, the file descriptor of the signal pipe is + * returned. + */ +int para_signal_init(void) +{ + if (!pipe(signal_pipe)) + return signal_pipe[0]; + PARA_EMERG_LOG("%s", "pipe error: Can not setup signal pipe"); + exit(EXIT_FAILURE); +} + +/* + * just write one integer to signal pipe + */ +static void generic_signal_handler(int s) +{ + write(signal_pipe[1], &s, sizeof(int)); + //fprintf(stderr, "got sig %i, write returned %d\n", s, ret); +} + +/** + * reap one child + * + * call waitpid() and print a log message containing the pid + * and the cause of the child's death. + * + * \return Like \p waitpid(), this function returns the process ID of the + * terminated child; on error, \p -E_WAITPID is returned. + * \sa waitpid(2) + */ +pid_t para_reap_child(void) +{ + int status; + pid_t pid = waitpid(-1, &status, WNOHANG); + + if (pid <= 0) { + if (pid < 0) + pid = -E_WAITPID; + return 0; + } + if (WIFEXITED(status)) + PARA_DEBUG_LOG("child %i exited. Exit status: %i\n", pid, + WEXITSTATUS(status)); + else if (WIFSIGNALED(status)) + PARA_DEBUG_LOG("child %i was killed by signal %i\n", pid, + WTERMSIG(status)); + else + PARA_WARNING_LOG("child %i terminated abormally\n", pid); + return pid; +} + +/** + * paraslash's zombie killer + * + * It just calls \p para_reap_child() until there are no more children left to + * reap. + */ +void para_reap_children(void) +{ + while (para_reap_child() > 0) + ; /* nothing */ +} + +/** + * wrapper around signal(2) + * \param sig the number of the signal to catch + * + * This installs the generic signal handler for the given signal. + * \return This function returns 1 on success and \p -E_SIGNAL_SIG_ERR on errors. + * \sa signal(2) + */ +int para_install_sighandler(int sig) +{ + PARA_DEBUG_LOG("catching signal %d\n", sig); + return signal(sig, &generic_signal_handler) == SIG_ERR? -E_SIGNAL_SIG_ERR : 1; +} + +/** + * return number of next pending signal + * + * This should be called if the fd for the signal pipe is ready for reading. + * + * \return On success, the number of the received signal is returned. \p + * -E_SIGNAL_READ is returned if a read error occured while reading the signal + * pipe. If the read was interrupted by another signal the function returns 0. + */ +int para_next_signal(void) +{ + int s; + ssize_t r; + + if ((r = read(signal_pipe[0], &s, sizeof(s)) == sizeof(s)) > 0) { + PARA_DEBUG_LOG("next signal: %d\n", s); + return s; + } + return r < 0 && (errno != EAGAIN)? 0 : -E_SIGNAL_READ; +} diff --git a/skencil/overview.sk b/skencil/overview.sk new file mode 100644 index 00000000..f4427df2 --- /dev/null +++ b/skencil/overview.sk @@ -0,0 +1,445 @@ +##Sketch 1 2 +document() +layout('A4',0) +layer('Layer 1',1,1,0,0,(0,0,0)) +G() +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(14) +txt('SQL query',(3.06152e-16,1,-1,3.06152e-16,73.5128,678.229)) +lw(0.992126) +la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0)) +b() +bs(76.5508,667.712,0) +bs(76.5508,750.57,0) +G_() +G() +lw(1) +la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0)) +b() +bs(205.995,666.16,0) +bs(205.995,744.222,0) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(14) +txt('open file',(1.19433e-15,1,-1,1.19433e-15,202.957,680.502)) +G_() +G() +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(14) +txt('mp3 data',(-1.83691e-16,-1,1,-1.83691e-16,241.524,730.112)) +lw(0.992126) +la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0)) +b() +bs(238.486,739.793,0) +bs(238.486,668.714,0) +G_() +G() +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(14) +txt('fork & exec',(1.19433e-15,1,-1,1.19433e-15,215.967,398.919)) +lw(0.992126) +la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0)) +b() +bs(219.005,388.674,0) +bs(219.005,476.027,0) +G_() +G() +lw(0.992126) +la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0)) +b() +bs(238.331,474.579,0) +bs(238.331,392.999,0) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(14) +txt('server status',(-1.83691e-16,-1,1,-1.83691e-16,241.369,468.586)) +G_() +lw(0.992126) +la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0)) +b() +bs(145.824,181.165,0) +bs(228.568,181.165,0) +lw(0.992126) +la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0)) +b() +bs(228.568,161.165,0) +bs(145.824,161.165,0) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(14) +txt('connect local socket',(1.19433e-15,1,-1,1.19433e-15,272.988,207.639)) +lw(0.992126) +la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0)) +b() +bs(307.4,339.815,0) +bs(307.4,200,0) +lw(0.992126) +la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0)) +b() +bs(280,200,0) +bs(280,339.815,0) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(14) +txt('server & audiod status',(-1.83691e-16,-1,1,-1.83691e-16,310.438,332.705)) +G() +lw(0.992126) +la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0)) +b() +bs(99.2303,144.289,0) +bs(99.2303,73.1918,0) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(14) +txt('s&a status',(-1.83691e-16,-1,1,-1.83691e-16,102.268,137.713)) +G_() +G() +lw(0.992126) +la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0)) +b() +bs(61.1648,150.482,0) +bs(61.1647,71.2323,0) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(14) +txt('cmd output',(-1.83691e-16,-1,1,-1.83691e-16,64.2028,142.553)) +G_() +lw(0.992126) +la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0)) +b() +bs(498.256,150.683,0) +bs(498.256,72.1559,0) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(14) +txt('pcm data',(-1.83691e-16,-1,1,-1.83691e-16,481.11,134.249)) +lw(0.992126) +ld((0.10000000000000001, 1.0)) +b() +bs(24.7094,792.443,0) +bs(310.724,792.443,0) +bs(310.421,682.725,0) +bs(561.98,682.585,0) +bs(561.98,609.679,0) +bs(24.7094,609.679,0) +bs(24.7094,792.443,0) +bC() +fp((0,0,0)) +le() +lw(1) +Fn('Times-Italic') +txt('server',(523.826,666.279)) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Italic') +txt('client',(334.671,487.931)) +G() +lw(0.992126) +la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0)) +b() +bs(105.06,750.094,0) +bs(105.06,667.236,0) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(14) +txt('mysql result',(-1.83691e-16,-1,1,-1.83691e-16,108.098,743.084)) +G_() +G() +lw(1) +r(78.7206,0,0,-28.2587,185.03,786.713) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('storage',(189.734,766.992)) +G_() +G() +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('mysqld',(35.977,766.992)) +lw(1) +r(75.6929,0,0,-28.2587,33.4706,786.713) +G_() +G() +lw(1) +r(119.09,0,0,-28.2587,39.0214,375.322) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('para_client',(45.2504,355.601)) +G_() +lw(1) +r(119.09,0,0,-28.2587,180.229,509.717) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('para_client',(186.458,489.996)) +G() +lw(1) +r(143.312,0,0,-28.2587,413.631,59.6823) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('sound_device',(419.299,39.9609)) +G_() +G() +lw(1) +r(94.8684,0,0,-28.2587,34.9844,58.6731) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('terminal',(42.4226,38.9517)) +G_() +G() +lw(1) +r(131.201,0,0,-28.2587,237.937,189.4) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('para_audioc',(244.894,169.678)) +G_() +G() +lw(1) +r(94.8684,0,0,-28.2587,34.9844,189.4) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('para_gui',(40.4306,169.678)) +G_() +lw(1) +r(114.044,0,0,-28.2587,441.89,188.865) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('para_play',(448.5,169.679)) +G() +G() +lw(0.992126) +la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0)) +b() +bs(216.8,522.254,0) +bs(216.8,603.141,0) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(14) +txt('connect tcp',(1.19433e-15,1,-1,1.19433e-15,213.762,530.624)) +G_() +G() +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(14) +txt('server status',(-1.83691e-16,-1,1,-1.83691e-16,249.561,597.495)) +lw(0.992126) +la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0)) +b() +bs(246.523,603.141,0) +bs(246.523,522.254,0) +G_() +G_() +fp((0.9,0.9,0.9)) +lw(1) +r(177.037,0,0,-89.6831,384.699,788.574) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Bold') +Fs(36) +txt('paraslash',(399.201,755.055)) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Bold') +Fs(36) +txt('0.2.9',(437.217,715.454)) +G() +lw(1) +r(517.739,0,0,-28.2587,33.4706,653.823) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('para_server',(236.372,634.102)) +G_() +G() +lw(1) +r(360.298,0,0,-28.2587,199.586,375.322) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('para_audiod',(320.419,355.601)) +G_() +G() +G() +lw(0.992126) +la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0)) +b() +bs(514.857,324.515,0) +bs(514.857,209.912,0) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(14) +txt('wav data',(-1.83691e-16,-1,1,-1.83691e-16,517.895,292.288)) +G_() +G() +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(14) +txt('fork & exec',(-1.83691e-16,-1,1,-1.83691e-16,484.591,300.646)) +lw(0.992126) +la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0)) +b() +bs(481.553,324.515,0) +bs(481.553,209.912,0) +G_() +G_() +G() +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(14) +txt('fork & exec',(153.764,182.009)) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(14) +txt('s&a status',(158.223,162.009)) +G_() +G() +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(14) +txt('fork & exec',(1.19433e-15,1,-1,1.19433e-15,70.3615,232.519)) +lw(0.992126) +la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0)) +b() +bs(73.3994,201.263,0) +bs(73.3995,330.639,0) +G_() +G() +lw(0.992126) +la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0)) +b() +bs(103.54,330.639,0) +bs(103.539,201.263,0) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(14) +txt('cmd output',(-1.83691e-16,-1,1,-1.83691e-16,106.577,297.647)) +G_() +G() +lw(1) +la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0)) +b() +bs(505.839,595.876,0) +bs(505.839,400.592,0) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(14) +txt('mp3 data ',(-1.83691e-16,-1,1,-1.83691e-16,508.877,525.842)) +G_() +G() +G() +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(14) +txt('connect tcp, send cmd',(2.57235e-15,1,-1,2.57235e-15,64.0626,431.071)) +lw(0.992126) +la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0)) +b() +bs(67.1006,390.148,0) +bs(67.1006,596.411,0) +G_() +G() +lw(0.992126) +la2(([(-6.0, 3.0), (1.0, 0.0), (-6.0, -3.0)], 0)) +b() +bs(101.501,596.411,0) +bs(101.501,390.148,0) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(14) +txt('cmd output',(-1.83691e-16,-1,1,-1.83691e-16,104.539,524.976)) +G_() +G_() +lw(0.992126) +ld((0.10000000000000001, 1.0)) +b() +bs(29.1803,383.603,0) +bs(164.433,383.603,0) +bs(164.433,513.811,0) +bs(396.545,513.811,0) +bs(396.545,383.568,0) +bs(570.132,383.568,0) +bs(570.132,26.3198,0) +bs(27.1619,26.3198,0) +bs(27.1619,382.595,0) +guidelayer('Guide Lines',1,0,0,1,(0,0,1)) +guide(-307.905,0) +grid((0,0,20,20),0,(0,0,1),'Grid') diff --git a/slider.c b/slider.c new file mode 100644 index 00000000..558f55bd --- /dev/null +++ b/slider.c @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2004-2006 Andre Noll + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ + +/** \file slider.c libzmw-based stream creator for paraslash */ + +#include "para.h" +#include "zmw/zmw.h" +#include "math.h" +#include "string.h" + +#define WIDTH 300 +#define SLIDER_RATIO 6 +#define VAL_2_SL_VAL(v) (v) * (SLIDER_RATIO - 1.0) / SLIDER_RATIO +#define SL_VAL_2_VAL(v) (v) * SLIDER_RATIO / (SLIDER_RATIO - 1.0) +#define VAL_2_SCORE(v) (v) > 0.5? 1 / (1.03 - (v)) / (1.03 - (v)) :\ + - 1 / ((v) + 0.03) / ((v) + 0.03) + +#define RGB(R,G,B) (((R)<<12) + ((G)<<6) + (B)) + +#define EPSILON 0.001 +#define LASTPLAYED_FORMULA(v) \ + 1 / (pow((v), 10) + EPSILON * EPSILON) - 1 / (1 + EPSILON) + EPSILON + +#define NUMPLAYED_FORMULA(v)\ + 10 * (v) + (v) / (1 - (v) * (1 - EPSILON)) + + +static int argc; +static char **argv; +char *streamname = NULL; +static Zmw_Float_0_1 *slider_vals, lastplayed_val, numplayed_val; + +#if 0 +static void boxed_text(const char *text, int i) +{ + ZMW(zmw_decorator(Zmw_Decorator_Border_Embossed)) + { + if (i == 2) + { + zmw_horizontal_expand(Zmw_False) ; + zmw_vertical_expand(Zmw_False) ; + } + zmw_label(text) ; + } +} +#endif + +void para_log(int ll, char* fmt,...) /* no logging */ +{ +} + +static void rst(void) +{ + int i; + for (i = 1; argv[i]; i++) + slider_vals[i] = VAL_2_SL_VAL(.5); + if (streamname) + free(streamname); + streamname = para_strdup("slider"); + //printf("rst: \n"); +} + +static char *get_value_filename(void) +{ + char *ret, *home =para_homedir(); + ret = make_message("%s/.paraslash/slide.values", home); + free(home); + return ret; +} + +static void load(void) +{ + char *tmp; + char att[999]; + FILE *f; + int i; + Zmw_Float_0_1 val; + + tmp = get_value_filename(); + f = fopen(tmp, "r"); + free(tmp); + if (!f) + return; + while (fscanf(f, "%900s %g", att, &val) == 2) { + if (!strcmp(att, "lastplayed")) { + lastplayed_val = val; + continue; + } + if (!strcmp(att, "numplayed")) { + numplayed_val = val; + continue; + } + for (i = 1; argv[i]; i++) + if (!strcmp(argv[i], att)) { + slider_vals[i] = val; + break; + } + + } + fclose(f); +} + +static void save(void) +{ + char *filename; + FILE *f; + int i; + + filename = get_value_filename(); + f = fopen(filename, "w"); + free(filename); + if (!f) + return; + fprintf(f, "lastplayed %g\n", lastplayed_val); + fprintf(f, "numplayed %g\n", numplayed_val); + for (i = 1; argv[i]; i++) + fprintf(f, "%s %g\n", argv[i], slider_vals[i]); + fclose(f); +} + + +static void print_score(FILE *fd) +{ + int i; + + for (i = 1; argv[i]; i++) { + if (SL_VAL_2_VAL(slider_vals[i]) > .99) + fprintf(fd, "deny: IS_N_SET(%s)\n", argv[i]); + if (slider_vals[i] < .01) + fprintf(fd, "deny: IS_SET(%s)\n", argv[i]); + } + fprintf(fd, "score: 0 "); + for (i = 1; argv[i]; i++) { + if (slider_vals[i] < .01) + continue; + fprintf(fd, "+ %i * IS_SET(%s) ", + (int) (VAL_2_SCORE(SL_VAL_2_VAL(slider_vals[i]))), + argv[i] + ); + } + fprintf(fd, " + round((LASTPLAYED()/1440 - 1000 / (LASTPLAYED()/1440 + 1)) / %f" + " - %f * NUMPLAYED(), 2)\n", + LASTPLAYED_FORMULA(SL_VAL_2_VAL(lastplayed_val)), + NUMPLAYED_FORMULA(SL_VAL_2_VAL(numplayed_val))); +} + +static void stradd(void) +{ + FILE *pipe; + char *cmd = make_message("para_client stradd %s", streamname); + + pipe = popen(cmd, "w"); + free(cmd); + if (!pipe) + return; + print_score(pipe); + pclose(pipe); +} + +static void b_save(void) +{ + stradd(); + save(); +} + +static void go(void) +{ + stradd(); + system("para_client cs slider"); + system("para_client stop"); + system("para_client play"); +} + +static void ok() +{ + stradd(); + system("para_client cs slider"); + system("para_client play"); + save(); + zmw_main_quit(EXIT_SUCCESS); +} + +static void make_buttons(void) +{ + /* Add a frame around the box */ + ZMW(zmw_decorator(Zmw_Decorator_Border_Embossed)) { + /* the buttons */ + ZMW(zmw_hbox()) { + zmw_button("save"); + if (zmw_activated()) + b_save(); + zmw_button("ok"); + if (zmw_activated()) + ok(); + zmw_button("go!"); + if (zmw_activated()) + go(); + zmw_button("rst"); + if (zmw_activated()) + rst(); +// zmw_x(WIDTH); + zmw_button("abort"); + if (zmw_activated()) + zmw_main_quit(EXIT_FAILURE); + } + } +} +static void make_stream_input_field(void) +{ + static char *text1 = NULL; + static int cursor_pos = 1; // Must be static + static int text1_length ; + + ZMW(zmw_vbox()) { + if ( text1 == NULL ) { + // The initial value must be malloced + text1 = strdup("slider") ; + text1_length = strlen(text1) ; + } + ZMW(zmw_fixed()) { + zmw_y(6); + zmw_x(0); + zmw_label("stream: "); + zmw_color(Zmw_Color_Foreground, RGB(63, 63, 63)); + zmw_y(0); + zmw_x(100); + zmw_width(WIDTH); + zmw_entry_with_cursor(&streamname, &cursor_pos); + if (zmw_changed()) + text1_length = strlen(text1); + } + } +} + +/* the sliders, one for each member in argv */ +static void make_sliders(void) +{ + int i; + char *txt; + + zmw_height(ZMW_VALUE_UNDEFINED); +// zmw_horizontal_expand(Zmw_True); + ZMW(zmw_hbox()) { + ZMW(zmw_vbox()) { + zmw_color(Zmw_Color_Foreground, RGB(0, 63, 63)); /* font */ + txt = make_message("lp: (%i%%)", + (int)(.5 + 100 * SL_VAL_2_VAL(lastplayed_val))); + zmw_label(txt); + free(txt); + txt = make_message("np: (%i%%)", + (int)(.5 + 100 * SL_VAL_2_VAL(numplayed_val))); + zmw_label(txt); + free(txt); + zmw_color(Zmw_Color_Foreground, RGB(63, 63, 0)); /* font */ + for (i = 1; argv[i]; i++) { + txt = make_message("%s: (%i%%)", argv[i], + (int)(.5 + 100 * SL_VAL_2_VAL(slider_vals[i]))); + zmw_label(txt); + free(txt); + } + } + ZMW(zmw_vbox()) { + zmw_width(200) ; + zmw_hscrollbar(&lastplayed_val, 1.0 / SLIDER_RATIO); + zmw_hscrollbar(&numplayed_val, 1.0 / SLIDER_RATIO); + for (i = 1; argv[i]; i++) { + zmw_hscrollbar(&slider_vals[i], + 1.0 / SLIDER_RATIO); + } + } + } +} + +static void anchor(void) +{ + + zmw_font_family("terminal"); + zmw_font_size(14); + zmw_rgb(0.1, 0.1, 0.1); + zmw_color(Zmw_Color_Foreground, RGB(63, 63, 0)); /* font */ + zmw_color(Zmw_Color_Background_Pushed, RGB(0, 10, 4)); /* slider bg */ + zmw_color(Zmw_Color_Background_Poped, RGB(0, 42, 42)); /* button bg */ + zmw_color(Zmw_Color_Border_Light, RGB(0, 7, 63)); /* slider/button border */ + + ZMW(zmw_window("para_slider")) { + ZMW(zmw_vbox()) { + make_buttons(); + make_stream_input_field(); + zmw_color(Zmw_Color_Foreground, RGB(0, 63, 63)); /* font */ +// make_special_sliders(); + zmw_color(Zmw_Color_Foreground, RGB(63, 63, 0)); /* font */ + make_sliders(); + } + } +} + +static void get_atts(void) +{ + char buf[256]; + FILE *p = popen("para_client laa", "r"); + argv = para_malloc(255 * sizeof(char *)); /* FIXME */ + + argv[0] = para_strdup("all_attributes"); + argc = 1; + if (!p) + goto err_out; + while (fgets(buf, 255, p)) { + chop(buf); + argv[argc] = para_strdup(buf); + argc++; + } + pclose(p); + argv[argc] = NULL; + argc--; + if (argc > 1) + return; /* success */ +err_out: + if (argc > 1) + for (argc = 1; argv[argc]; argc++) + free(argv[argc]); + free(argv); + argc = 1; +} + +int main(int the_argc, char *the_argv[]) +{ + argc = the_argc; + argv = the_argv; + if (argc < 2) { + get_atts(); + if (argc < 2) + return -1; + } + slider_vals = para_malloc((argc + 1) * sizeof(int*)); + rst(); + load(); +// printf("argc: %d\n", argc); +// for (i = 1; argv[i]; i++) +// printf("argv[%d]: %s\n", i, argv[i]); + zmw_init(&argc, &argv); + zmw_main(anchor); + return 0; +} diff --git a/stat.c b/stat.c new file mode 100644 index 00000000..7550f2d6 --- /dev/null +++ b/stat.c @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2005-2006 Andre Noll + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ + +/** + * \file stat.c functions used for sending/receiving the status of para_server + * and para_audiod + */ +#include "para.h" +#include "close_on_fork.h" +#include "list.h" +#include "error.h" +#include "string.h" + +/** the maximal number of simultaneous connections */ +#define MAX_STAT_CLIENTS 50 + +/** + * The structure associated with a connected client that sent the 'stat' command +* + * + * A status client is identified by its file descriptor. para_audiod + * keeps a list of connected status clients. + */ +struct stat_client { +/** + * + * + * the stat client's file descriptor + */ +int fd; +/** + * + * its entry in the list of stat clients +*/ +struct list_head node; +}; + +static struct list_head client_list; +static int initialized; +static int num_clients; + +/** + * the list of all status items sent by para_server/para_audiod + */ +const char *status_item_list[NUM_STAT_ITEMS] = { + [SI_STATUS_BAR] = "status_bar", + [SI_STATUS] = "status", + [SI_NUM_PLAYED] = "num_played", + + [SI_MTIME] = "mtime", + [SI_LENGTH_MIN] = "length_min", + [SI_LENGTH_SEC] = "length_sec", + + [SI_FILE_SIZE] = "file_size", + [SI_STATUS_FLAGS] = "status_flags", + [SI_FORMAT] = "format", + + [SI_SCORE] = "score", + [SI_AUDIO_INFO1] = "audio_file_info1", + [SI_AUDIO_INFO2] = "audio_file_info2", + + [SI_AUDIO_INFO3] = "audio_file_info3", + [SI_DBINFO1] = "dbinfo1", + [SI_DBINFO2] = "dbinfo2", + + [SI_DBINFO3] = "dbinfo3", + [SI_DECODER_FLAGS] = "decoder_flags", + [SI_AUDIOD_STATUS] = "audiod_status", + + [SI_PLAY_TIME] = "play_time", + [SI_UPTIME] = "uptime", + [SI_OFFSET] = "offset", + + [SI_LENGTH] = "length", + [SI_STREAM_START] = "stream_start", + [SI_CURRENT_TIME] = "current_time", + + [SI_AUDIOD_UPTIME] = "audiod_uptime", + [SI_DBTOOL] = "dbtool" +}; +#define FOR_EACH_STAT_ITEM(i) for (i = 0; i < NUM_STAT_ITEMS; i++) + +static void dump_stat_client_list(void) +{ + struct stat_client *sc; + + if (!initialized) + return; + list_for_each_entry(sc, &client_list, node) + PARA_INFO_LOG("stat client on fd %d\n", sc->fd); +} +/** + * add a status client to the list + * + * \return Positive value on success, or -E_TOO_MANY_CLIENTS if + * the number of connected clients exceeds #MAX_STAT_CLIENTS + */ +int stat_client_add(int fd) +{ + struct stat_client *new_client; + + if (num_clients >= MAX_STAT_CLIENTS) { + PARA_ERROR_LOG("maximal number of stat clients (%d) exceeded\n", + MAX_STAT_CLIENTS); + return -E_TOO_MANY_CLIENTS; + } + if (!initialized) { + INIT_LIST_HEAD(&client_list); + initialized = 1; + } + PARA_INFO_LOG("adding client on fd %d\n", fd); + new_client = para_malloc(sizeof(struct stat_client)); + new_client->fd = fd; + add_close_on_fork_list(fd); + list_add(&new_client->node, &client_list); + dump_stat_client_list(); + num_clients++; + return 1; +} +/** + * write a message to all connected stat clients + * + * \param msg a \p NULL terminated buffer + */ +void stat_client_write(char *msg) +{ + struct stat_client *sc, *tmp; + char *buf; + ssize_t len; + struct timeval tv = {0 , 0}; + + if (!initialized) + return; + buf = make_message("%s\n", msg); + len = strlen(buf); + list_for_each_entry_safe(sc, tmp, &client_list, node) { + int fd = sc->fd, ret; + fd_set wfds; + FD_ZERO(&wfds); + FD_SET(fd, &wfds); +// PARA_DEBUG_LOG("%s: p=%lx\n", __func__, (long)p); + do + ret = select(fd + 1, NULL, &wfds, NULL, &tv); + while (ret < 0 && errno == EINTR); + if (ret) { + ret = write(fd, buf, len); + PARA_DEBUG_LOG("dumped %s to fd %d, ret = %d\n", msg, fd, ret); + if (ret == len ) + continue; + } + /* write error or fd not ready for writing */ + close(fd); + del_close_on_fork_list(fd); + num_clients--; + PARA_INFO_LOG("deleting client on fd %d\n", fd); + list_del(&sc->node); + free(sc); + dump_stat_client_list(); + } + free(buf); + PARA_DEBUG_LOG("%d client(s)\n", num_clients); +} + +/** + * send empty status list + * + * Send to each connected client the full status item list + * with empty values. + */ +void dump_empty_status(void) +{ + int i; + char *empty_items = NULL; + + if (!initialized) + return; + FOR_EACH_STAT_ITEM(i) { + char *tmp = make_message("%s%s:\n", empty_items? empty_items : "", + status_item_list[i]); + free(empty_items); + empty_items = tmp; + } +// PARA_DEBUG_LOG("%s: empty items: %s\n", __func__, empty_items); + stat_client_write(empty_items); + free(empty_items); +} + +/** + * check if line starts with known status item. + * + * \param line buffer containing the line + * + * \return If the beginning of \a line matches any paraslash status item and is + * followed by a colon, the number of that status item is returned. Otherwise, + * this function returns \p -E_UNKNOWN_STAT_ITEM. + */ +int stat_line_valid(const char *line) +{ + int i; + size_t line_len; + + if (!line || !*line) + return -E_UNKNOWN_STAT_ITEM; + line_len = strlen(line); + for (i = 0; i < NUM_STAT_ITEMS; i++) { + const char *s = status_item_list[i]; + size_t item_len = strlen(s); + + if (line_len > item_len && line[item_len] == ':' && + !strncmp(line, s, item_len)) + return i; + } + return -E_UNKNOWN_STAT_ITEM; +} + +/** + * call a custom function for each complete line + * + * \param buf the buffer containing data seperated by newlines + * \param n the number of bytes in \a buf + * \param line_handler the custom function + * \param num upper bound on calls to \a line_handler + * + * If \a line_handler is \p NULL, return number of complete lines in buf. + * Otherwise, call \a line_handler for each complete line, but no more than \a num + * times. If \a num is zero, there is no restriction on how often \a line_handler + * may be called. The rest of the buffer (last chunk containing incomplete line + * if \a num is zero) is moved to the beginning of the buffer. + * + * \return If line_handler is not NULL, this function returns the number + * of bytes not handled to \a line_handler. + */ +unsigned for_each_line(char *buf, int n, void (*line_handler)(char *), int num) +{ + char *start = buf, *end; + int i, num_lines = 0; + + while (start < buf + n) { + char *next_null; + char *next_cr; + + next_cr = memchr(start, '\n', buf + n - start); + next_null = memchr(start, '\0', buf + n - start); + if (!next_cr && !next_null) + break; + if (next_cr && next_null) { + end = next_cr < next_null? next_cr : next_null; + } else if (next_null) { + end = next_null; + } else + end = next_cr; + num_lines++; + if (line_handler) { + *end = '\0'; + line_handler(start); + start = ++end; + if (num && num_lines >= num) + break; + } else + start = ++end; + } + if (!line_handler) + return num_lines; + i = buf + n - start; + if (i && i != n) + memmove(buf, start, i); + return i; +} + diff --git a/string.c b/string.c new file mode 100644 index 00000000..07f54a07 --- /dev/null +++ b/string.c @@ -0,0 +1,447 @@ +/* + * Copyright (C) 2004-2006 Andre Noll + * + * 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 /* gettimeofday */ +#include "para.h" +#include +#include +#include /* uname() */ +#include "error.h" +#include "string.h" + +/** + * paraslash's version of realloc() + * + * \param p pointer to the memory block, may be NULL + * \param size desired new size + * + * A wrapper for realloc(3). It calls \p exit(\p EXIT_FAILURE) on errors, + * i.e. there is no need to check the return value in the caller. + * \sa realloc(3) + */ +__must_check __malloc void *para_realloc(void *p, size_t size) +{ + /* + * No need to check for NULL pointers: If p is NULL, the call + * to realloc is equivalent to malloc(size) + */ + if (!(p = realloc(p, size))) { + PARA_EMERG_LOG("%s", "realloc failed, aborting\n"); + exit(EXIT_FAILURE); + } + return p; +} + +/** + * paraslash's version of malloc() + * + * \param size desired new size + * + * A wrapper for malloc(3) which exits on errors. + * \sa malloc(3) + */ +__must_check __malloc void *para_malloc(size_t size) +{ + void *p = malloc(size); + + if (!p) { + PARA_EMERG_LOG("%s", "malloc failed, aborting\n"); + exit(EXIT_FAILURE); + } + return p; +} + +/** + * paraslash's version of calloc() + * + * \param size desired new size + * + * A wrapper for calloc(3) which exits on errors. + * \sa calloc(3) + */ +__must_check __malloc void *para_calloc(size_t size) +{ + void *ret = para_malloc(size); + + memset(ret, 0, size); + return ret; +} + +/** + * paraslash's version of strdup() + * + * \param s: string to be duplicated + * + * A wrapper for strdup(3). It calls exit(EXIT_FAILURE) on + * errors, i.e. there is no need to check the return value in the caller. + * Moreover, this wrapper checks for \a s being NULL and returns an empty + * string in this case. + * + * \sa strdup(3) + */ +__must_check __malloc char *para_strdup(const char *s) +{ + char *ret; + + if ((ret = strdup(s? s: ""))) + return ret; + PARA_EMERG_LOG("%s", "strdup failed, aborting\n"); + exit(EXIT_FAILURE); +} + +/** + * allocate a sufficiently large string and print into it + * + * \param fmt usual format string + * + * Produce output according to \a fmt. No artificial bound on the length of the + * resulting string is imposed. This function either returns a pointer to a + * string that must be freed by the caller or aborts without returning. + * + * \sa printf(3) + */ +__must_check __printf_1_2 __malloc char *make_message(const char *fmt, ...) +{ + char *msg; + + PARA_VSPRINTF(fmt, msg); + return msg; +} + +/** + * paraslash's version of strcat() + * + * \param a string to be appended to + * \param b string to append + * + * Append \a b to \a a. If \a a is NULL, return a copy of \a b, i.e. + * para_strcat(NULL, b) is equivalent to para_strdup(b). If \a b is NULL, + * return \a a without making a copy of \a a. Otherwise, construct the + * concatenation \a c, free \a a (but not \a b) and return \a c. + * + * \sa strcat(3) + */ +__must_check __malloc char *para_strcat(char *a, const char *b) +{ + char *tmp; + + if (!a) + return para_strdup(b); + if (!b) + return a; + tmp = make_message("%s%s", a, b); + free(a); + return tmp; +} + +/** + * paraslash's version of dirname() + * + * \param name pointer to The full path + * + * If \a name is \þ NULL or the empty string, return \p NULL, Otherwise, Make a + * copy of \a name and return its directory component. Caller is responsible to + * free the result. + */ +__must_check __malloc char *para_dirname(const char *name) +{ + char *p, *ret; + + if (!name || !*name) + return NULL; + ret = para_strdup(name); + p = strrchr(ret, '/'); + if (!p) + *ret = '\0'; + else + *p = '\0'; + return ret; +} + +/** + * paraslash's version of basename() + * + * \param name Pointer to the full path + * + * If \a name is \p NULL or the empty string, return \p NULL, Otherwise, make a + * copy of \a name and return its filename component. Caller is responsible to + * free the result. + */ +__must_check __malloc char *para_basename(const char *name) +{ + char *p; + + if (!name || !*name) + return NULL; + p = strrchr(name, '/'); + if (!p) + return para_strdup(name); + p++; + if (!*p) + return NULL; + return para_strdup(p); +} + +/** + * simple search and replace routine + * + * \param src source string + * \param macro_name the name of the macro + * \param replacement the replacement format string + * + * Replace \a macro_name(arg) by \a replacement. \a replacement is a format + * string which may contain a single string conversion specifier which gets + * replaced by 'arg'. + * + * \return A string in which all matches in \a src are replaced, or NULL if \a + * macro_name was not found in \a src. Caller must free the result. + * + * \sa regcomp(3) + */ +__must_check __malloc char *s_a_r(const char *src, const char* macro_name, + const char *replacement) +{ + regex_t preg; + size_t nmatch = 1; + regmatch_t pmatch[1]; + int eflags = 0; + char *dest = NULL; + const char *bufptr = src; + + if (!macro_name || !replacement || !src) + return para_strdup(src); + regcomp(&preg, macro_name, 0); + while (regexec(&preg, bufptr, nmatch, pmatch, eflags) + != REG_NOMATCH) { + char *tmp, *arg, *o_bracket, *c_bracket; + + o_bracket = strchr(bufptr + pmatch[0].rm_so, '('); + c_bracket = o_bracket? strchr(o_bracket, ')') : NULL; + if (!c_bracket) + goto out; + tmp = para_strdup(bufptr); + tmp[pmatch[0].rm_so] = '\0'; + dest = para_strcat(dest, tmp); + free(tmp); + + arg = para_strdup(o_bracket + 1); + arg[c_bracket - o_bracket - 1] = '\0'; + tmp = make_message(replacement, arg); + free(arg); + dest = para_strcat(dest, tmp); + free(tmp); + bufptr = c_bracket; + bufptr++; + } + dest = para_strcat(dest, bufptr); +// PARA_DEBUG_LOG("%s: returning %s\n", __func__, dest); +out: + regfree(&preg); + return dest; +} + +/** + * replace a string according to a list of macros + * + * \param macro_list the array containing a macro/replacement pairs. + * \param src the source string + * + * This function just calls s_a_r() for each element of \a macro_list. + */ +__must_check __malloc char *s_a_r_list(struct para_macro *macro_list, char *src) +{ + struct para_macro *mp = macro_list; + char *ret = NULL, *tmp = para_strdup(src); + + while (mp->name) { + ret = s_a_r(tmp, mp->name, mp->replacement); + free(tmp); + if (!ret) + return src; /* FIXME: shouldn't we continue here? */ + tmp = ret; + mp++; + } + //PARA_DEBUG_LOG("%s: returning %s\n", __func__, dest); + return ret; +} + +/** + * cut trailing newline + * + * \param buf the string to be chopped. + * + * Replace the last character in \a buf by zero if it is euqal to + * the newline character. + */ +void chop(char *buf) +{ + int n = strlen(buf); + if (!n) + return; + if (buf[n - 1] == '\n') + buf[n - 1] = '\0'; +} + +/** + * get a random filename + * + * This is by no means a secure way to create temporary files in a hostile + * direcory like /tmp. However, it is OK to use for temp files, fifos, sockets + * that are created in ~/.paraslash. Result must be freed by the caller. + */ +__must_check __malloc char *para_tmpname(void) +{ + struct timeval now; + gettimeofday(&now, NULL); + srand(now.tv_usec); + return make_message("%08i", rand()); +} + +/** + * create unique temporary file + * + * \param template the template to be passed to mkstemp() + * \param mode the desired mode of the tempfile + * + * This wrapper for mkstemp additionally uses fchmod() to + * set the given mode of the tempfile if mkstemp() returned success. + * Return value: The file descriptor of the temp file just created on success. + * On errors, -E_MKSTEMP or -E_FCHMOD is returned. + */ +__must_check int para_mkstemp(char *template, mode_t mode) +{ + int tmp, fd = mkstemp(template); + + if (fd < 0) + return -E_MKSTEMP; + tmp = fchmod(fd, mode); + if (tmp >= 0) + return fd; + close(fd); + unlink(template); + return -E_FCHMOD; +} + +/** + * get the logname of the current user + * + * \return A dynammically allocated string that must be freed by the caller. On + * errors, the string "unknown user" is returned, i.e. this function never + * returns NULL. + */ +__must_check __malloc char *para_logname(void) +{ + struct passwd *pw = getpwuid(getuid()); + return para_strdup(pw? pw->pw_name : "unknown_user"); +} + +/** + * get the home directory of the current user + * + * \return A dynammically allocated string that must be freed by the caller. If + * the home directory could not be found, this function returns "/tmp". + */ +__must_check __malloc char *para_homedir(void) +{ + struct passwd *pw = getpwuid(getuid()); + return para_strdup(pw? pw->pw_dir : "/tmp"); +} + +/** + * split string and return pointers to its parts. + * + * \param args the string to be split + * \param argv_ptr pointer to the list of substrings + * \param delim delimiter + * + * This function modifies \a args by replacing each occurance of \a delim by + * zero. A NULL-terminated array of pointers to char* is allocated dynamically + * and these pointers are initialized to point to the broken-up substrings + * within \a args. A pointer to this array is returned via \a argv_ptr. + * + * \return The number of substrings found in \a args. + */ +__must_check unsigned split_args(char *args, char ***argv_ptr, int delim) +{ + char *p = args; + char **argv; + ssize_t n = 0, i; + + while ((p = strchr(p, delim))) { + p++; + n++; + } + *argv_ptr = para_malloc((n + 3) * sizeof(char *)); + argv = *argv_ptr; + i = 0; + p = args; +// printf("split_args: a:%s\n", p); + while (p) { + argv[i] = p; + p = strchr(p, delim); + if (p) { +// printf("a:%s\n", p); + *p = '\0'; +// printf("b:\n"); + p++; + i++; + } + } + argv[n + 1] = NULL; + return n; +} + +/** + * ensure that file descriptors 0, 1, and 2 are valid + * + * Common approach that opens /dev/null until it gets a file descriptor greater + * than two. + * + * \sa okir's Black Hats Manual. + */ +void valid_fd_012(void) +{ + while (1) { + int fd; + + fd = open("/dev/null", O_RDWR); + if (fd < 0) + exit(EXIT_FAILURE); + if (fd > 2) { + close(fd); + break; + } + } +} + +/** + * get the own hostname + * + * \return A dynammically allocated string containing the hostname. + * + * \sa uname(2) + */ +__malloc char *para_hostname(void) +{ + struct utsname u; + + uname(&u); + return para_strdup(u.nodename); +} diff --git a/string.h b/string.h new file mode 100644 index 00000000..9fb42b26 --- /dev/null +++ b/string.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2006 Andre Noll + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ + +/** \file string.h exported sybmols from string.c */ + +/** contains name/replacement pairs used by s_a_r_list() + * + * \sa s_a_r() + */ +struct para_macro { + /** the name of the macro */ + char *name; + /** the replacement text */ + char *replacement; +}; +__must_check __malloc void *para_realloc(void *p, size_t size); +__must_check __malloc void *para_malloc(size_t size); +__must_check __malloc void *para_calloc(size_t size); +__must_check __malloc char *para_strdup(const char *s); +__must_check __malloc __printf_1_2 char *make_message(const char *fmt, ...); +__must_check __malloc char *para_strcat(char *a, const char *b); +__must_check __malloc char *para_dirname(const char *name); +__must_check __malloc char *para_basename(const char *name); +__must_check __malloc char *s_a_r(const char *src, const char* regex, const char *replacement); +__must_check __malloc char *s_a_r_list(struct para_macro *pm, char *src); +void chop(char* buf); +__must_check __malloc char *para_tmpname(void); +__must_check int para_mkstemp(char *template, mode_t mode); +__must_check __malloc char *para_logname(void); +__must_check __malloc char *para_homedir(void); +__must_check unsigned split_args(char *args, char ***argv_ptr, int delim); +__malloc char *para_hostname(void); +void valid_fd_012(void); + diff --git a/time.c b/time.c new file mode 100644 index 00000000..f659b16f --- /dev/null +++ b/time.c @@ -0,0 +1,165 @@ +#include "para.h" +/** \file time.c helper functions for dealing with timeouts */ + +/** + * convert struct timeval to milliseconds + * + * \param tv the value to convert + * + * \return The number off milliseconds in \a tv + */ +long unsigned tv2ms(const struct timeval *tv) +{ + return tv->tv_sec * 1000 + (tv->tv_usec + 500)/ 1000; +} + +/** + * convert milliseconds to struct timeval + * + * \param n the number of milliseconds + * \param tv will be filled in by the function + */ +void ms2tv(long unsigned n, struct timeval *tv) +{ + tv->tv_sec = n / 1000; + tv->tv_usec = (n % 1000) * 1000; +} + +/** + * convert double to struct timeval + * + * \param x the value to convert + * \param tv converted value is stored here + */ +void d2tv(double x, struct timeval *tv) +{ + tv->tv_sec = x; + tv->tv_usec = (x - (double)tv->tv_sec) * 1000.0 * 1000.0 + 0.5; +} + +/** + * compute the difference of two time values + * + * \param b minuend + * \param a subtrahend + * \param diff difference is stored here + * + * \return If b > a, compute b - a and return 1. if b < a + * compute a - b and return -1. If \a diff is not \p NULL, \a diff gets + * filled in with the absolute value |b - a|. + */ +int tv_diff(const struct timeval *b, const struct timeval *a, struct timeval *diff) +{ + int ret = 1; + + if ((b->tv_sec < a->tv_sec) || + ((b->tv_sec == a->tv_sec) && (b->tv_usec < a->tv_usec))) { + const struct timeval *tmp = a; + a = b; + b = tmp; + ret = -1; + } + if (!diff) + return ret; + diff->tv_sec = b->tv_sec - a->tv_sec; + if (b->tv_usec < a->tv_usec) { + diff->tv_sec--; + diff->tv_usec = 1000 * 1000 - a->tv_usec + b->tv_usec; + } else + diff->tv_usec = b->tv_usec - a->tv_usec; + return ret; +} + +/** + * add two structs of type timeval + * + * \param a first addend + * \param b second addend + * \param sum holds the sum upon return + */ +void tv_add(const struct timeval *a, const struct timeval *b, + struct timeval *sum) +{ + sum->tv_sec = a->tv_sec + b->tv_sec; + if (a->tv_usec + b->tv_usec >= 1000 * 1000) { + sum->tv_sec++; + sum->tv_usec = a->tv_usec + b->tv_usec - 1000 * 1000; + } else + sum->tv_usec = a->tv_usec + b->tv_usec; +} + +/** + * compute integer multiple of given struct timeval + * + * \param mult the integer value to multiply with + * \param tv the timevalue to multiply + * \param result holds mult * tv upon return + */ +void tv_scale(const unsigned long mult, const struct timeval *tv, + struct timeval *result) +{ + result->tv_sec = mult * tv->tv_sec; + result->tv_sec += tv->tv_usec * mult / 1000 / 1000; + result->tv_usec = tv->tv_usec * mult % (1000 * 1000); +} + +/** + * compute fraction of given struct timeval + * + * \param div the integer value to divide by + * \param tv the timevalue to divide + * \param result holds (1 / mult) * tv upon return + */ +void tv_divide(const unsigned long div, const struct timeval *tv, + struct timeval *result) +{ + long unsigned q = tv->tv_usec / div; + result->tv_sec = tv->tv_sec / div; + result->tv_usec = (tv->tv_sec - result->tv_sec * div) * 1000 * 1000 / div; + if (result->tv_usec + q >= 1000 * 1000) { + result->tv_sec++; + result->tv_usec = 1000 * 1000 - result->tv_usec - q; + } else + result->tv_usec += q; +} + +/** + * compute a convex combination of two time values + * + * \param a the first coefiicent + * \param tv1 the first time value + * \param b the second coefiicent + * \param tv2 the second time value + * \param result holds the convex combination upon return + * + * compute x := (a * tv1 + b * tv2) / (|a| + |b|). a, b may be negative. + * returns sign(x) and stores |x| in the result pointer. + */ +int tv_convex_combination(const long a, const struct timeval *tv1, + const long b, const struct timeval *tv2, + struct timeval *result) +{ + struct timeval tmp1, tmp2, tmp3; + int ret = 1, subtract = ((a > 0 && b < 0) || (a < 0 && b > 0)); + unsigned long a1 = ABS(a), b1 = ABS(b); + + tv_scale(a1, tv1, &tmp1); + tv_scale(b1, tv2, &tmp2); + if (subtract) + ret = tv_diff(&tmp1, &tmp2, &tmp3); + else + tv_add(&tmp1, &tmp2, &tmp3); + if (a1 + b1) + tv_divide(a1 + b1, &tmp3, result); + else { + result->tv_sec = 0; + result->tv_usec = 0; + } + if (!a || !b) { + if (a + b < 0) + ret = -1; + } else + if (a < 0) + ret = -ret; + return ret; +} diff --git a/versions/paraslash-0.0.99.tgz b/versions/paraslash-0.0.99.tgz new file mode 100644 index 00000000..0dc68c5b Binary files /dev/null and b/versions/paraslash-0.0.99.tgz differ diff --git a/versions/paraslash-0.0.99.tgz.asc b/versions/paraslash-0.0.99.tgz.asc new file mode 100644 index 00000000..2f503905 --- /dev/null +++ b/versions/paraslash-0.0.99.tgz.asc @@ -0,0 +1,7 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.2.5 (GNU/Linux) + +iD8DBQBBcp6/Wto1QDEAkw8RAiLVAKCI8AyDm8nnIlUVTdMjPXvFiiigYwCgpcbP +yRYrzN5SJeJGB9BMQwy93FA= +=9IMv +-----END PGP SIGNATURE----- diff --git a/versions/paraslash-0.1.0.tgz b/versions/paraslash-0.1.0.tgz new file mode 100644 index 00000000..b063c8df Binary files /dev/null and b/versions/paraslash-0.1.0.tgz differ diff --git a/versions/paraslash-0.1.0.tgz.asc b/versions/paraslash-0.1.0.tgz.asc new file mode 100644 index 00000000..d4dc3e73 --- /dev/null +++ b/versions/paraslash-0.1.0.tgz.asc @@ -0,0 +1,7 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.2.5 (GNU/Linux) + +iD8DBQBBeNU/Wto1QDEAkw8RAk+fAJ9gcyVaAWC80zkBBrIOn+xe1PYa5gCfeDkl +mLT5IExWAQLATFGpfoXjwgY= +=8MrY +-----END PGP SIGNATURE----- diff --git a/versions/paraslash-0.1.1.tgz b/versions/paraslash-0.1.1.tgz new file mode 100644 index 00000000..108e3514 Binary files /dev/null and b/versions/paraslash-0.1.1.tgz differ diff --git a/versions/paraslash-0.1.1.tgz.asc b/versions/paraslash-0.1.1.tgz.asc new file mode 100644 index 00000000..64dc0695 --- /dev/null +++ b/versions/paraslash-0.1.1.tgz.asc @@ -0,0 +1,7 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.2.5 (GNU/Linux) + +iD8DBQBBi4NJWto1QDEAkw8RAo8LAJ9QorQ8F1/XAs3w124O2vrZYREFmgCgkm1c +8Wk5758Y3KXL/gqhZ30FwYk= +=rA4D +-----END PGP SIGNATURE----- diff --git a/versions/paraslash-0.1.2.tgz b/versions/paraslash-0.1.2.tgz new file mode 100644 index 00000000..93de422f Binary files /dev/null and b/versions/paraslash-0.1.2.tgz differ diff --git a/versions/paraslash-0.1.2.tgz.asc b/versions/paraslash-0.1.2.tgz.asc new file mode 100644 index 00000000..02f01d2d --- /dev/null +++ b/versions/paraslash-0.1.2.tgz.asc @@ -0,0 +1,7 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.2.5 (GNU/Linux) + +iD8DBQBBqjEFWto1QDEAkw8RAhlHAJ0Qncfz/82teh5tNXHcH2iJ31uXiwCggoeo +A+pdWH4KH8gR3bETOR0vKY8= +=8KSZ +-----END PGP SIGNATURE----- diff --git a/versions/paraslash-0.1.3.tgz b/versions/paraslash-0.1.3.tgz new file mode 100644 index 00000000..1314d45a Binary files /dev/null and b/versions/paraslash-0.1.3.tgz differ diff --git a/versions/paraslash-0.1.3.tgz.asc b/versions/paraslash-0.1.3.tgz.asc new file mode 100644 index 00000000..b94011e5 --- /dev/null +++ b/versions/paraslash-0.1.3.tgz.asc @@ -0,0 +1,7 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.2.5 (GNU/Linux) + +iD8DBQBBuYcDWto1QDEAkw8RAv4+AJ9Cg+RVDyGwVJQrJ4/pyslvEw5ENwCdGe+p +2k+K+awrnDwEiR8toLeKzg8= +=5703 +-----END PGP SIGNATURE----- diff --git a/versions/paraslash-0.1.4.tgz b/versions/paraslash-0.1.4.tgz new file mode 100644 index 00000000..b98c6826 Binary files /dev/null and b/versions/paraslash-0.1.4.tgz differ diff --git a/versions/paraslash-0.1.4.tgz.asc b/versions/paraslash-0.1.4.tgz.asc new file mode 100644 index 00000000..81432d84 --- /dev/null +++ b/versions/paraslash-0.1.4.tgz.asc @@ -0,0 +1,7 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.2.5 (GNU/Linux) + +iD8DBQBBxaDnWto1QDEAkw8RAo4HAKCUoXYmraULQ3j1Vpskdp+mxrv8vQCdE2r2 +QkqQRJvrwih7YjOoZsgZNyo= +=ZlPC +-----END PGP SIGNATURE----- diff --git a/versions/paraslash-0.1.5.tgz b/versions/paraslash-0.1.5.tgz new file mode 100644 index 00000000..59f85fad Binary files /dev/null and b/versions/paraslash-0.1.5.tgz differ diff --git a/versions/paraslash-0.1.5.tgz.asc b/versions/paraslash-0.1.5.tgz.asc new file mode 100644 index 00000000..fc717b85 --- /dev/null +++ b/versions/paraslash-0.1.5.tgz.asc @@ -0,0 +1,7 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.2.5 (GNU/Linux) + +iD8DBQBB1YTyWto1QDEAkw8RAsrnAKCChqFSupAeJ6KxN/zu45tmLgDOawCfY2Sv +oQvFqa+fnW4Sok8QIQTEHVc= +=1btl +-----END PGP SIGNATURE----- diff --git a/versions/paraslash-0.1.6.tgz b/versions/paraslash-0.1.6.tgz new file mode 100644 index 00000000..5c55cd2d Binary files /dev/null and b/versions/paraslash-0.1.6.tgz differ diff --git a/versions/paraslash-0.1.6.tgz.asc b/versions/paraslash-0.1.6.tgz.asc new file mode 100644 index 00000000..0811d45f --- /dev/null +++ b/versions/paraslash-0.1.6.tgz.asc @@ -0,0 +1,7 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.2.5 (GNU/Linux) + +iD8DBQBCKbbOWto1QDEAkw8RAi8cAJkB0J1J917amcXhaIGrPOEwsh3N6wCffIkg +0SbjPqZmjj0ysd07nkbfF9Y= +=Tpn/ +-----END PGP SIGNATURE----- diff --git a/versions/paraslash-0.1.7.tgz b/versions/paraslash-0.1.7.tgz new file mode 100644 index 00000000..4d2b5844 Binary files /dev/null and b/versions/paraslash-0.1.7.tgz differ diff --git a/versions/paraslash-0.1.7.tgz.asc b/versions/paraslash-0.1.7.tgz.asc new file mode 100644 index 00000000..6baf00f9 --- /dev/null +++ b/versions/paraslash-0.1.7.tgz.asc @@ -0,0 +1,7 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.2.5 (GNU/Linux) + +iD8DBQBCY+EgWto1QDEAkw8RAnKpAKCNDVXhmRbovvbtPh9B839k0I4UTACfTcM1 +37Omc/mYNF5t9RWCTDZXzMw= +=UGev +-----END PGP SIGNATURE----- diff --git a/versions/paraslash-0.2.0.tar.bz2 b/versions/paraslash-0.2.0.tar.bz2 new file mode 100644 index 00000000..88e6a083 Binary files /dev/null and b/versions/paraslash-0.2.0.tar.bz2 differ diff --git a/versions/paraslash-0.2.0.tar.bz2.asc b/versions/paraslash-0.2.0.tar.bz2.asc new file mode 100644 index 00000000..60ed064b --- /dev/null +++ b/versions/paraslash-0.2.0.tar.bz2.asc @@ -0,0 +1,7 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.2 (GNU/Linux) + +iD8DBQBC9MmQWto1QDEAkw8RAicGAJ4+v+FAAuMkRp5ldeJOogt0HVz7YwCggaU1 +9X5kv752BC04Rnw9Z5gnNkk= +=liTM +-----END PGP SIGNATURE----- diff --git a/versions/paraslash-0.2.1.tar.bz2 b/versions/paraslash-0.2.1.tar.bz2 new file mode 100644 index 00000000..51d1ff54 Binary files /dev/null and b/versions/paraslash-0.2.1.tar.bz2 differ diff --git a/versions/paraslash-0.2.1.tar.bz2.asc b/versions/paraslash-0.2.1.tar.bz2.asc new file mode 100644 index 00000000..b9dfb66b --- /dev/null +++ b/versions/paraslash-0.2.1.tar.bz2.asc @@ -0,0 +1,7 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.2 (GNU/Linux) + +iD8DBQBDAOfPWto1QDEAkw8RAkogAKCq1jAc5CzjCnRzzhRKvQJah2ZSOQCfSXjO +1emlvg4SXyru3GibEDufnzo= +=Rt/T +-----END PGP SIGNATURE----- diff --git a/versions/paraslash-0.2.10.tar.bz2 b/versions/paraslash-0.2.10.tar.bz2 new file mode 100644 index 00000000..4ba7cb98 Binary files /dev/null and b/versions/paraslash-0.2.10.tar.bz2 differ diff --git a/versions/paraslash-0.2.10.tar.bz2.asc b/versions/paraslash-0.2.10.tar.bz2.asc new file mode 100644 index 00000000..3c39ddc0 --- /dev/null +++ b/versions/paraslash-0.2.10.tar.bz2.asc @@ -0,0 +1,7 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.2.1 (GNU/Linux) + +iD8DBQBD9lLgWto1QDEAkw8RAg/NAJ9CbDqJWcC6T6eUxkwJXUzLO+fIuwCfUvOX +lDCOmC7Ekgfsj9bIn8DRgqg= +=xqpS +-----END PGP SIGNATURE----- diff --git a/versions/paraslash-0.2.2.tar.bz2 b/versions/paraslash-0.2.2.tar.bz2 new file mode 100644 index 00000000..f2f042fd Binary files /dev/null and b/versions/paraslash-0.2.2.tar.bz2 differ diff --git a/versions/paraslash-0.2.2.tar.bz2.asc b/versions/paraslash-0.2.2.tar.bz2.asc new file mode 100644 index 00000000..92cc7d49 --- /dev/null +++ b/versions/paraslash-0.2.2.tar.bz2.asc @@ -0,0 +1,7 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.2 (GNU/Linux) + +iD8DBQBDBUN5Wto1QDEAkw8RAjUXAJ9nGvPGVUGU+ohYNuwBA3ntWYAV6gCeNWTS +JruvcovSxpZ0/fGr47P3MX4= +=BomB +-----END PGP SIGNATURE----- diff --git a/versions/paraslash-0.2.3.tar.bz2 b/versions/paraslash-0.2.3.tar.bz2 new file mode 100644 index 00000000..63c28dad Binary files /dev/null and b/versions/paraslash-0.2.3.tar.bz2 differ diff --git a/versions/paraslash-0.2.3.tar.bz2.asc b/versions/paraslash-0.2.3.tar.bz2.asc new file mode 100644 index 00000000..abc8b5d0 --- /dev/null +++ b/versions/paraslash-0.2.3.tar.bz2.asc @@ -0,0 +1,7 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.2 (GNU/Linux) + +iD8DBQBDFygrWto1QDEAkw8RAhNwAJwMoTi7Z4UGJ8wOYQYemqKONC1D/gCfQL2e +7UZ4vgK6SoPgfAF71KTiois= +=Copg +-----END PGP SIGNATURE----- diff --git a/versions/paraslash-0.2.4.tar.bz2 b/versions/paraslash-0.2.4.tar.bz2 new file mode 100644 index 00000000..23405640 Binary files /dev/null and b/versions/paraslash-0.2.4.tar.bz2 differ diff --git a/versions/paraslash-0.2.4.tar.bz2.asc b/versions/paraslash-0.2.4.tar.bz2.asc new file mode 100644 index 00000000..585619cc --- /dev/null +++ b/versions/paraslash-0.2.4.tar.bz2.asc @@ -0,0 +1,7 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.2 (GNU/Linux) + +iD8DBQBDMYSLWto1QDEAkw8RAh9RAJ48UU3Ddo6vMaAKBCKlKixDzCMJkgCgioov +4gIO9N9Of7Xe+x2uV/qVc4Q= +=/C+7 +-----END PGP SIGNATURE----- diff --git a/versions/paraslash-0.2.5.tar.bz2 b/versions/paraslash-0.2.5.tar.bz2 new file mode 100644 index 00000000..8f815dd9 Binary files /dev/null and b/versions/paraslash-0.2.5.tar.bz2 differ diff --git a/versions/paraslash-0.2.5.tar.bz2.asc b/versions/paraslash-0.2.5.tar.bz2.asc new file mode 100644 index 00000000..991292c5 --- /dev/null +++ b/versions/paraslash-0.2.5.tar.bz2.asc @@ -0,0 +1,7 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.2 (GNU/Linux) + +iD8DBQBDTq27Wto1QDEAkw8RAim+AKCS97CJGamp5zWyTE4TVFhcXAfsqQCfVY5F +5u7hwO0LSFD7oken59eBaic= +=Rc4P +-----END PGP SIGNATURE----- diff --git a/versions/paraslash-0.2.6.tar.bz2 b/versions/paraslash-0.2.6.tar.bz2 new file mode 100644 index 00000000..a9ba26ed Binary files /dev/null and b/versions/paraslash-0.2.6.tar.bz2 differ diff --git a/versions/paraslash-0.2.6.tar.bz2.asc b/versions/paraslash-0.2.6.tar.bz2.asc new file mode 100644 index 00000000..bdae38ec --- /dev/null +++ b/versions/paraslash-0.2.6.tar.bz2.asc @@ -0,0 +1,7 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.2 (GNU/Linux) + +iD8DBQBDY7w5Wto1QDEAkw8RAsigAJ9H7V6Ox+r8YCQtwyqpogvv7hn3VQCfW9oS +kxHL+0dEFhh18oDFpFejUv4= +=/wPn +-----END PGP SIGNATURE----- diff --git a/versions/paraslash-0.2.7.tar.bz2 b/versions/paraslash-0.2.7.tar.bz2 new file mode 100644 index 00000000..76cd8c4a Binary files /dev/null and b/versions/paraslash-0.2.7.tar.bz2 differ diff --git a/versions/paraslash-0.2.7.tar.bz2.asc b/versions/paraslash-0.2.7.tar.bz2.asc new file mode 100644 index 00000000..f7ad24a6 --- /dev/null +++ b/versions/paraslash-0.2.7.tar.bz2.asc @@ -0,0 +1,7 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.2 (GNU/Linux) + +iD8DBQBDsYVZWto1QDEAkw8RAs4RAJ4hmW115pTB+eqI5HljZGAUxB9V4QCeOPRT +/nrMniVfSJ2iHHU3jtZfYrs= +=xiVS +-----END PGP SIGNATURE----- diff --git a/versions/paraslash-0.2.8.tar.bz2 b/versions/paraslash-0.2.8.tar.bz2 new file mode 100644 index 00000000..4aa7508f Binary files /dev/null and b/versions/paraslash-0.2.8.tar.bz2 differ diff --git a/versions/paraslash-0.2.8.tar.bz2.asc b/versions/paraslash-0.2.8.tar.bz2.asc new file mode 100644 index 00000000..72c2c8d4 --- /dev/null +++ b/versions/paraslash-0.2.8.tar.bz2.asc @@ -0,0 +1,7 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.2 (GNU/Linux) + +iD8DBQBDuLAWWto1QDEAkw8RAo3JAJ47Q5nCQKinSIddlAhjoU4hxpNpKgCghMd/ +TSR7233HKIudTw/4/RLgRUg= +=gaCj +-----END PGP SIGNATURE----- diff --git a/versions/paraslash-0.2.9.tar.bz2 b/versions/paraslash-0.2.9.tar.bz2 new file mode 100644 index 00000000..811387f6 Binary files /dev/null and b/versions/paraslash-0.2.9.tar.bz2 differ diff --git a/versions/paraslash-0.2.9.tar.bz2.asc b/versions/paraslash-0.2.9.tar.bz2.asc new file mode 100644 index 00000000..682aa009 --- /dev/null +++ b/versions/paraslash-0.2.9.tar.bz2.asc @@ -0,0 +1,7 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.2 (GNU/Linux) + +iD8DBQBD1aQpWto1QDEAkw8RAnxcAJ4y5Bccey0e5U1I7QdTM1reZtAtGACfUrD6 +vQtvLz1h+ULsQKfgylvHn08= +=7FUV +-----END PGP SIGNATURE----- diff --git a/wav.c b/wav.c new file mode 100644 index 00000000..647be101 --- /dev/null +++ b/wav.c @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2005-2006 Andre Noll + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ + +/** \file wav.c a filter that inserts a wave header */ + +#include "gcc-compat.h" +#include "para.h" + +#include "list.h" +#include "filter.h" +#include "string.h" + +#define WAV_OUTBUF_SIZE 81920 +#define WAV_HEADER_LEN 44 +#define BITS 16 + +/* derived from oggdec.c of the vorbis-tools-1.0.1 package */ +#define WRITE_U32(buf, x) (buf)[0] = (unsigned char)((x) & 0xff);\ + (buf)[1] = (unsigned char)(((x) >> 8) & 0xff);\ + (buf)[2] = (unsigned char)(((x) >> 16) & 0xff);\ + (buf)[3] = (unsigned char)(((x) >> 24) & 0xff); +#define WRITE_U16(buf, x) (buf)[0] = (unsigned char)((x) & 0xff); + +static void make_wav_header(int channels, int samplerate, struct filter_node *fn) +{ + + char *headbuf = fn->buf; + unsigned int size = 0x7fffffff; + int bytespersec = channels * samplerate * BITS / 8; + int align = channels * BITS / 8; + + PARA_DEBUG_LOG("writing wave header: %d channels, %d KHz\n", channels, samplerate); + memset(headbuf, 0, WAV_HEADER_LEN); + memcpy(headbuf, "RIFF", 4); + WRITE_U32(headbuf + 4, size - 8); + memcpy(headbuf + 8, "WAVE", 4); + memcpy(headbuf + 12, "fmt ", 4); + WRITE_U32(headbuf + 16, 16); + WRITE_U16(headbuf + 20, 1); /* format */ + WRITE_U16(headbuf + 22, channels); + WRITE_U32(headbuf + 24, samplerate); + WRITE_U32(headbuf + 28, bytespersec); + WRITE_U16(headbuf + 32, align); + WRITE_U16(headbuf + 34, BITS); + memcpy(headbuf + 36, "data", 4); + WRITE_U32(headbuf + 40, size - 44); +} + +static ssize_t wav_convert(char *inbuf, size_t len, struct filter_node *fn) +{ + size_t copy; + int *bof = fn->private_data; + + if (*bof) { + make_wav_header(fn->fci->channels, fn->fci->samplerate, fn); + fn->loaded = WAV_HEADER_LEN; + *bof = 0; +// return 0; + } + copy = MIN(len, fn->bufsize - fn->loaded); + memmove(fn->buf + fn->loaded, inbuf, copy); + fn->loaded += copy; +// PARA_DEBUG_LOG("len = %d, copy = %d\n", len, copy); + return copy; +} + +static void wav_close(struct filter_node *fn) +{ + free(fn->buf); + fn->buf = NULL; + free(fn->private_data); + fn->private_data = NULL; +} + +static void wav_open(struct filter_node *fn) +{ + int *bof; + + fn->bufsize = WAV_OUTBUF_SIZE; + fn->buf = para_malloc(fn->bufsize); + fn->private_data = para_malloc(sizeof(int)); + bof = fn->private_data; + *bof = 1; + PARA_DEBUG_LOG("wav filter node: %p, output buffer: %p, loaded: %d\n", + fn, fn->buf, fn->loaded); +} + +void wav_init(struct filter *f) +{ + f->convert = wav_convert; + f->close = wav_close; + f->open = wav_open; +} diff --git a/web/contact.in.html b/web/contact.in.html new file mode 100644 index 00000000..66125070 --- /dev/null +++ b/web/contact.in.html @@ -0,0 +1,9 @@ +

Contact

+ +

André Noll, maan@systemlinux.org

+ +Comments and bug reports are welcome (english, german, or spanish +language). Please provide enough info such as the version of paraslash +you are using and relevant parts of the logs. Including the string +[paraslash] in the subject line is also a good idea. diff --git a/web/demo.in.html b/web/demo.in.html new file mode 100644 index 00000000..130e6745 --- /dev/null +++ b/web/demo.in.html @@ -0,0 +1,28 @@ +

Live Demo

+ +

There is a public paraslash stream at www.paraslash.org, + streaming + + the music of Henri Petterson. + + + You can listen to the stream with any mp3 player that supports + http streaming. Both

+ +

mpg123 -Z http://www.paraslash.org:8009/

+ + and + +

xmms http://www.paraslash.org:8009/

+ +

are known to work.

+ +

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 + + this shell script + + on your Unix system. No root-privileges are required.

+ diff --git a/web/documentation.in.html b/web/documentation.in.html new file mode 100644 index 00000000..deaa45f6 --- /dev/null +++ b/web/documentation.in.html @@ -0,0 +1,25 @@ +

Documentation

+

+Have a look at this + overview, +a pdf file containing a sketch which illustrates how the pieces of paraslash work +together. Read + README +for general information (including a list of required software), + INSTALL +for installation notes, and + README.mysql +for instructions on how to use the mysql database tool +shipped with paraslash.

+

+The various commands of para_server and para_audiod are explained in +paraslash's + manual pages. +

+

+As of version 0.2.10, the source of para_server and para_audiod is fully documented. +Have a look at the + Paraslash API Reference. +

+

Finally, you can RTFS online. +

diff --git a/web/download.in.html b/web/download.in.html new file mode 100644 index 00000000..fcdc1fe1 --- /dev/null +++ b/web/download.in.html @@ -0,0 +1,15 @@ +

Download

+ +

Only source is available, +including the +nightly cvs snapshot (which may or may not compile at +any given time). All regular releases are +cryptographically signed. +Anonymous (read-only) cvs access is also +available. Checkout a copy with

+ +

cvs -d +:pserver:anonymous@cvs.systemlinux.org:/var/lib/cvs +login

+

(empty passwd)

+

cvs -d :pserver:anonymous@cvs.systemlinux.org:/var/lib/cvs co paraslash

diff --git a/web/footer.html b/web/footer.html new file mode 100644 index 00000000..d9c9f058 --- /dev/null +++ b/web/footer.html @@ -0,0 +1,14 @@ +
+ +

+ Valid HTML 4.01! +

+--> + + + + diff --git a/web/header.html b/web/header.html new file mode 100644 index 00000000..c59f9158 --- /dev/null +++ b/web/header.html @@ -0,0 +1,40 @@ + + + + + Paraslash + + + + + + + + + + + + +
+ paraslash
+
+

Paraslash: Play, archive, rate and stream + large audio sets happily

+ + A set of tools for doing just what its name + suggests. +
+
Home +
News +
Features +
Download +
Screenshots +
Live Demo +
Changelog +
Documentation +
License +
Contact +
Credits +
+
diff --git a/web/index.in.html b/web/index.in.html new file mode 100644 index 00000000..d2f28a4f --- /dev/null +++ b/web/index.in.html @@ -0,0 +1,28 @@ +

Events

+
    +
  • 2006-02-17: paraslash-0.2.10 + (sig) "cyclic attractor"
  • +
  • 2006-02-16: API Reference online
  • +
  • 2006-01-24: paraslash-0.2.9 "progressive turbulence"
  • +
  • 2006-01-02: paraslash-0.2.8 "dynamic accumulation"
  • +
  • 2005-12-27: paraslash-0.2.7 "transparent invariance"
  • +
  • 2005-11-12: new web pages
  • +
  • 2005-10-29: paraslash-0.2.6 "recursive compensation"
  • +
  • 2005-10-27: manual pages online
  • +
  • 2005-10-13: paraslash-0.2.5 "aggressive resolution"
  • +
  • 2005-09-21: paraslash-0.2.4 "toxic anticipation"
  • +
  • 2005-09-01: paraslash-0.2.3 "hydrophilic movement"
  • +
  • 2005-08-19: paraslash-0.2.2 "tangential excitation"
  • +
  • 2005-08-15: paraslash-0.2.1 "surreal experience"
  • +
  • 2005-08-06: overview.pdf
  • +
  • 2005-08-06: paraslash-0.2.0 "distributed diffusion"
  • +
  • 2005-08-01: paraslash live stream
  • +
  • 2005-04-18: paraslash-0.1.7 "melting penetration"
  • +
  • 2005-03-05: paraslash-0.1.6 "asymptotic balance"
  • +
  • 2004-12-31: paraslash-0.1.5 "opaque eternity"
  • +
  • 2004-12-19: paraslash-0.1.4 "tunneling transition"
  • +
  • 2004-12-10: paraslash-0.1.3 "vanishing inertia"
  • +
  • 2004-11-28: paraslash-0.1.2 "spherical fluctuation"
  • +
  • 2004-11-05: paraslash-0.1.1 "floating atmosphere"
  • +
  • 2004-10-22: paraslash-0.1.0 "rotating cortex"
  • +
diff --git a/web/license.in.html b/web/license.in.html new file mode 100644 index 00000000..055e436e --- /dev/null +++ b/web/license.in.html @@ -0,0 +1,13 @@ +

License

+ +Paraslash is licensed under the GNU General +Public License, which is generally just abbreviated +as the GPL license, or just the GPL. Check out the +GPL +License Quiz or take a look at the Frequently Asked +Questions about the GNU GPL. For the differences between +the established version 2 and the upcoming version 3 of the GPL, see this + colored +diff. diff --git a/web/para.css b/web/para.css new file mode 100644 index 00000000..62925efb --- /dev/null +++ b/web/para.css @@ -0,0 +1,310 @@ +BODY,H1,H2,H3,H4,H5,H6,P,CENTER,TD,TH,UL,DL,DIV { + font-family: Geneva, Arial, Helvetica, sans-serif; +} +BODY,TD { + font-size: 90%; +} +H1 { + text-align: center; + font-size: 160%; +} +H2 { + font-size: 120%; +} +H3 { + font-size: 100%; +} +CAPTION { font-weight: bold } +DIV.qindex { + width: 100%; + background-color: #482e02; + border: 1px solid #84b007; + text-align: center; + margin: 2px; + padding: 2px; + line-height: 140%; +} +DIV.nav { + width: 100%; + background-color: #48ee02; + border: 1px solid #84b007; + text-align: center; + margin: 2px; + padding: 2px; + line-height: 140%; +} +DIV.navtab { + background-color: #48ee02; + border: 1px solid #84b007; + text-align: center; + margin: 2px; + margin-right: 15px; + padding: 2px; +} +TD.navtab { + font-size: 70%; +} +A.qindex { + text-decoration: none; + font-weight: bold; + color: #1A419D; +} +A.qindex:visited { + text-decoration: none; + font-weight: bold; + color: #1A419D +} +A.qindex:hover { + text-decoration: none; + background-color: #ffff00; +} +A.qindexHL { + text-decoration: none; + font-weight: bold; + background-color: #cccc00; + color: #000000; + border: 1px double #929502; +} +A.qindexHL:hover { + text-decoration: none; + background-color: #ffff00; + color: #000000; +} +A.qindexHL:visited { text-decoration: none; background-color: #e6c60c; color: #000000 } +A.el { text-decoration: none; font-weight: bold } +A.elRef { font-weight: bold } +A.code:link { text-decoration: none; font-weight: normal; color: ##BA3708} +A.code:visited { text-decoration: none; font-weight: normal; color: ##BA3708} +A.codeRef:link { font-weight: normal; color: #BA3708} +A.codeRef:visited { font-weight: normal; color: #BA3708} +A:hover { text-decoration: none; background-color: #ffff00 } +DL.el { margin-left: -1cm } +.fragment { + font-family: Fixed, monospace; + font-size: 95%; +} +PRE.fragment { + border: 1px solid #CCCCCC; + background-color: #351505; + margin-top: 4px; + margin-bottom: 4px; + margin-left: 2px; + margin-right: 8px; + padding-left: 6px; + padding-right: 6px; + padding-top: 4px; + padding-bottom: 4px; +} +DIV.ah { background-color: black; font-weight: bold; color: #000000; margin-bottom: 3px; margin-top: 3px } +TD.md { background-color: #112244; font-weight: bold; } +TD.mdPrefix { + background-color: #112244; + color: #606060; + font-size: 80%; +} +TD.mdname1 { background-color: #112244; font-weight: bold; color: #00CC00; } +TD.mdname { background-color: #112244; font-weight: bold; color: #00CC00; width: 600px; } +DIV.groupHeader { + margin-left: 16px; + margin-top: 12px; + margin-bottom: 6px; + font-weight: bold; +} +DIV.groupText { margin-left: 16px; font-style: italic; font-size: 90% } +BODY { + background: black; + color: #bbbbbb; + margin-right: 20px; + margin-left: 20px; +} +TD.indexkey { + background-color: #383e12; + font-weight: bold; + padding-right : 10px; + padding-top : 2px; + padding-left : 10px; + padding-bottom : 2px; + margin-left : 0px; + margin-right : 0px; + margin-top : 2px; + margin-bottom : 2px; + border: 1px solid #CCCCCC; +} +TD.indexvalue { + background-color: #383e12; + font-style: italic; + padding-right : 10px; + padding-top : 2px; + padding-left : 10px; + padding-bottom : 2px; + margin-left : 0px; + margin-right : 0px; + margin-top : 2px; + margin-bottom : 2px; + border: 1px solid #CCCCCC; +} +TR.memlist { + background-color: #112244; +} +P.formulaDsp { text-align: center; } +IMG.formulaDsp { } +IMG.formulaInl { vertical-align: middle; } +SPAN.keyword { color: #008000 } +SPAN.keywordtype { color: #00CCCC } +SPAN.keywordflow { color: #108000 } +SPAN.comment { color: #00CCCC } +SPAN.preprocessor { color: #CC00CC } +SPAN.stringliteral { color: #e0e020 } +SPAN.charliteral { color: #0000ff } +.mdTable { + border: 1px solid #868686; + background-color: #112244; +} +.mdRow { + padding: 8px 10px; +} +.mdescLeft { + padding: 0px 8px 4px 8px; + font-size: 80%; + font-style: italic; + background-color: #000000; + border-top: 1px none #10E010; + border-right: 1px none #202010; + border-bottom: 1px none #202010; + border-left: 1px none #202010; + margin: 0px; +} +.mdescRight { + padding: 0px 8px 4px 8px; + font-size: 80%; + font-style: italic; + background-color: #000000; + border-top: 1px none #20E210; + border-right: 1px none #202010; + border-bottom: 1px none #202010; + border-left: 1px none #202010; + margin: 0px; +} +.memItemLeft { + padding: 1px 0px 0px 8px; + margin: 4px; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + border-top-color: #FFFF10; + border-right-color: #FFFF10; + border-bottom-color: #FFFF10; + border-left-color: #FFFF10; + border-top-style: solid; + border-right-style: none; + border-bottom-style: none; + border-left-style: none; + background-color: #000000; + font-size: 80%; +} +.memItemRight { + padding: 1px 8px 0px 8px; + margin: 4px; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + border-top-color: #E0E000; + border-right-color: #E0E000; + border-bottom-color: #E0E000; + border-left-color: #E0E000; + border-top-style: solid; + border-right-style: none; + border-bottom-style: none; + border-left-style: none; + background-color: #000000; + font-size: 80%; +} +.memTemplItemLeft { + padding: 1px 0px 0px 8px; + margin: 4px; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + border-top-color: #E0E000; + border-right-color: #E0E000; + border-bottom-color: #E0E000; + border-left-color: #E0E000; + border-top-style: none; + border-right-style: none; + border-bottom-style: none; + border-left-style: none; + background-color: #000000; + font-size: 80%; +} +.memTemplItemRight { + padding: 1px 8px 0px 8px; + margin: 4px; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + border-top-color: #E0E000; + border-right-color: #E0E000; + border-bottom-color: #E0E000; + border-left-color: #E0E000; + border-top-style: none; + border-right-style: none; + border-bottom-style: none; + border-left-style: none; + background-color: #000000; + font-size: 80%; +} +.memTemplParams { + padding: 1px 0px 0px 8px; + margin: 4px; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + border-top-color: #E0E000; + border-right-color: #E0E000; + border-bottom-color: #E0E000; + border-left-color: #E0E000; + border-top-style: solid; + border-right-style: none; + border-bottom-style: none; + border-left-style: none; + color: #606060; + background-color: #3A3A1A; + font-size: 80%; +} +.search { color: #f03309; + font-weight: bold; +} +FORM.search { + margin-bottom: 0px; + margin-top: 0px; +} +INPUT.search { font-size: 75%; + color: #800000; + font-weight: normal; + background-color: #000000; +} +TD.tiny { font-size: 75%; +} +a { + color: #BA4108; +} +a:visited { + color: #BA3708; +} +.dirtab { padding: 4px; + border-collapse: collapse; + border: 1px solid #84b007; +} +TH.dirtab { background: #080e02; + font-weight: bold; +} +HR { height: 1px; + border: none; + border-top: 1px solid yellow; +} + diff --git a/web/screenshots.in.html b/web/screenshots.in.html new file mode 100644 index 00000000..6d14be12 --- /dev/null +++ b/web/screenshots.in.html @@ -0,0 +1,23 @@ +

Screenshots

+ +

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 + + para_server's logfile + +and a similar thing for the logfile of + + para_audiod.

+ +Moreover, there are screenshots of + + para_gui, + + para_sdl_gui, + + para_krell + +and + para_slider. +