Merge branch 'maint'
authorAndre Noll <maan@tuebingen.mpg.de>
Thu, 4 Nov 2021 16:10:34 +0000 (17:10 +0100)
committerAndre Noll <maan@tuebingen.mpg.de>
Thu, 4 Nov 2021 16:10:55 +0000 (17:10 +0100)
* maint:
  paraslash 0.5.9

264 files changed:
.gitignore
Doxyfile
INSTALL
Makefile.in
Makefile.real
NEWS.md
aac.h [deleted file]
aac_afh.c
aac_common.c [deleted file]
aacdec_filter.c
acl.c
acl.h
afh.c
afh.h
afh_common.c
afh_recv.c
afs.c
afs.cmd [deleted file]
afs.h
aft.c
alsa_mix.c
alsa_write.c
amp_filter.c
ao_write.c
attribute.c
audioc.c
audiod.c
audiod.cmd [deleted file]
audiod.h
audiod_command.c
autogen.sh
base64.h
bash_completion
bitstream.c
bitstream.h
blob.c
buffer_tree.c
buffer_tree.h
check_wav.c
check_wav.h
chunk_queue.c
chunk_queue.h
client.c
client.h
client_common.c
close_on_fork.c
close_on_fork.h
command.c
command.h
command_util.bash [deleted file]
compress_filter.c
configure.ac
crypt.c [deleted file]
crypt.h
crypt_backend.h
crypt_common.c
daemon.c
daemon.h
dccp_recv.c
dccp_send.c
error.h
exec.c
fade.c [deleted file]
fd.c
fd.h
fecdec_filter.c
file_write.c
filter.c
filter.h
filter_common.c
flac_afh.c
flacdec_filter.c
gcrypt.c
ggo.c [deleted file]
ggo.h [deleted file]
grab_client.c
grab_client.h
gui.c
gui.h
gui_theme.c
http_recv.c
http_send.c
imdct.c
interactive.c
interactive.h
ipc.c
ipc.h
lsu.c [new file with mode: 0644]
lsu.h [new file with mode: 0644]
m4/gengetopt/afh.m4 [deleted file]
m4/gengetopt/afh_recv.m4 [deleted file]
m4/gengetopt/alsa_write.m4 [deleted file]
m4/gengetopt/amp_filter.m4 [deleted file]
m4/gengetopt/ao_write.m4 [deleted file]
m4/gengetopt/audioc.m4 [deleted file]
m4/gengetopt/audiod.m4 [deleted file]
m4/gengetopt/channels.m4 [deleted file]
m4/gengetopt/client.m4 [deleted file]
m4/gengetopt/color.m4 [deleted file]
m4/gengetopt/complete.m4 [deleted file]
m4/gengetopt/compress_filter.m4 [deleted file]
m4/gengetopt/config_file.m4 [deleted file]
m4/gengetopt/daemon.m4 [deleted file]
m4/gengetopt/dccp_recv.m4 [deleted file]
m4/gengetopt/fade.m4 [deleted file]
m4/gengetopt/file_write.m4 [deleted file]
m4/gengetopt/filter.m4 [deleted file]
m4/gengetopt/group.m4 [deleted file]
m4/gengetopt/gui.m4 [deleted file]
m4/gengetopt/header.m4 [deleted file]
m4/gengetopt/history_file.m4 [deleted file]
m4/gengetopt/http_recv.m4 [deleted file]
m4/gengetopt/log_timing.m4 [deleted file]
m4/gengetopt/logfile.m4 [deleted file]
m4/gengetopt/loglevel.m4 [deleted file]
m4/gengetopt/makefile [deleted file]
m4/gengetopt/mp3dec_filter.m4 [deleted file]
m4/gengetopt/oss_write.m4 [deleted file]
m4/gengetopt/osx_write.m4 [deleted file]
m4/gengetopt/play.m4 [deleted file]
m4/gengetopt/prebuffer_filter.m4 [deleted file]
m4/gengetopt/priority.m4 [deleted file]
m4/gengetopt/recv.m4 [deleted file]
m4/gengetopt/resample_filter.m4 [deleted file]
m4/gengetopt/sample_format.m4 [deleted file]
m4/gengetopt/sample_rate.m4 [deleted file]
m4/gengetopt/server.m4 [deleted file]
m4/gengetopt/sync_filter.m4 [deleted file]
m4/gengetopt/udp_recv.m4 [deleted file]
m4/gengetopt/user.m4 [deleted file]
m4/gengetopt/write.m4 [deleted file]
m4/lls/afh.suite.m4 [new file with mode: 0644]
m4/lls/audioc.suite.m4 [new file with mode: 0644]
m4/lls/audiod.suite.m4 [new file with mode: 0644]
m4/lls/audiod_cmd.suite.m4 [new file with mode: 0644]
m4/lls/client.suite.m4 [new file with mode: 0644]
m4/lls/copyright.m4 [new file with mode: 0644]
m4/lls/filter.suite.m4 [new file with mode: 0644]
m4/lls/filter_cmd.suite.m4 [new file with mode: 0644]
m4/lls/gui.suite.m4 [new file with mode: 0644]
m4/lls/include/channels.m4 [new file with mode: 0644]
m4/lls/include/color.m4 [new file with mode: 0644]
m4/lls/include/common-option-section.m4 [new file with mode: 0644]
m4/lls/include/complete.m4 [new file with mode: 0644]
m4/lls/include/config-file.m4 [new file with mode: 0644]
m4/lls/include/daemon.m4 [new file with mode: 0644]
m4/lls/include/detailed-help.m4 [new file with mode: 0644]
m4/lls/include/group.m4 [new file with mode: 0644]
m4/lls/include/help.m4 [new file with mode: 0644]
m4/lls/include/history-file.m4 [new file with mode: 0644]
m4/lls/include/host.m4 [new file with mode: 0644]
m4/lls/include/log-timing.m4 [new file with mode: 0644]
m4/lls/include/logfile.m4 [new file with mode: 0644]
m4/lls/include/loglevel.m4 [new file with mode: 0644]
m4/lls/include/long-help.m4 [new file with mode: 0644]
m4/lls/include/per-command-options-section.m4 [new file with mode: 0644]
m4/lls/include/port.m4 [new file with mode: 0644]
m4/lls/include/priority.m4 [new file with mode: 0644]
m4/lls/include/sample-format.m4 [new file with mode: 0644]
m4/lls/include/sample-rate.m4 [new file with mode: 0644]
m4/lls/include/user.m4 [new file with mode: 0644]
m4/lls/include/version.m4 [new file with mode: 0644]
m4/lls/makefile [new file with mode: 0644]
m4/lls/mixer.suite.m4 [new file with mode: 0644]
m4/lls/play.suite.m4 [new file with mode: 0644]
m4/lls/play_cmd.suite.m4 [new file with mode: 0644]
m4/lls/recv.suite.m4 [new file with mode: 0644]
m4/lls/recv_cmd.suite.m4 [new file with mode: 0644]
m4/lls/server.suite.m4 [new file with mode: 0644]
m4/lls/server_cmd.suite.m4 [new file with mode: 0644]
m4/lls/write.suite.m4 [new file with mode: 0644]
m4/lls/write_cmd.suite.m4 [new file with mode: 0644]
man_util.bash [deleted file]
mix.h
mixer.c [new file with mode: 0644]
mm.c
mm.h
mood.c
mood.h
mp.c [new file with mode: 0644]
mp.h [new file with mode: 0644]
mp3_afh.c
mp3dec_filter.c
net.c
net.h
ogg_afh.c
ogg_afh_common.c
ogg_afh_common.h
oggdec_filter.c
openssl.c [new file with mode: 0644]
opus_afh.c
opus_common.h
opusdec_filter.c
oss_mix.c
oss_write.c
osx_write.c [deleted file]
para.h
play.c
play.cmd [deleted file]
playlist.c
portable_io.h
prebuffer_filter.c
recv.c
recv.h
recv_common.c
resample_filter.c
ringbuffer.c
ringbuffer.h
sched.c
sched.h
score.c
send.h
send_common.c
server.c
server.cmd [deleted file]
server.h
sideband.c
sideband.h
signal.c
signal.h
spx.h
spx_afh.c
spx_common.c
spxdec_filter.c
stat.c
stdin.c
stdin.h
stdout.c
stdout.h
string.c
string.h
sync_filter.c
t/makefile.test
t/t0001-oggdec-correctness.sh
t/t0004-server.sh
t/t0005-man.sh
t/test-lib.sh
time.c
udp_recv.c
udp_send.c
user_list.c
user_list.h
version.c
vss.c
vss.h
wav_filter.c
web/.htaccess
web/about.in.html
web/dia/overview.dia [deleted file]
web/documentation.in.html
web/download.in.html
web/images/paraslash.ico
web/images/paraslash.png
web/manual.md
wma_afh.c
wma_common.c
wmadec_filter.c
write.c
write.h
write_common.c
write_common.h [deleted file]
yy/makefile [new file with mode: 0644]
yy/mp.lex [new file with mode: 0644]
yy/mp.y [new file with mode: 0644]

index 10a7a8a..bd5e048 100644 (file)
@@ -15,7 +15,7 @@ config.status
 Makefile
 TODO
 paraslash-*.tar.bz2
-web/dia/overview.pdf
+paraslash-*.tar.xz
 *.swp
 *.rej
 *~
index c607b8e..b11683e 100644 (file)
--- a/Doxyfile
+++ b/Doxyfile
@@ -1,4 +1,4 @@
-# Doxyfile 1.8.6
+# Doxyfile 1.8.11
 
 # This file describes the settings to be used by the documentation system
 # doxygen (www.doxygen.org) for a project.
@@ -46,10 +46,10 @@ PROJECT_NUMBER         =
 
 PROJECT_BRIEF          =
 
-# With the PROJECT_LOGO tag one can specify an logo or icon that is included in
-# the documentation. The maximum height of the logo should not exceed 55 pixels
-# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo
-# to the output directory.
+# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
+# in the documentation. The maximum height of the logo should not exceed 55
+# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
+# the logo to the output directory.
 
 PROJECT_LOGO           =
 
@@ -60,7 +60,7 @@ PROJECT_LOGO           =
 
 OUTPUT_DIRECTORY       = web_sync/doxygen
 
-# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub-
+# 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
@@ -70,6 +70,14 @@ OUTPUT_DIRECTORY       = web_sync/doxygen
 
 CREATE_SUBDIRS         = NO
 
+# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
+# characters to appear in the names of generated files. If set to NO, non-ASCII
+# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
+# U+3044.
+# The default value is: NO.
+
+ALLOW_UNICODE_NAMES    = 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.
@@ -85,14 +93,14 @@ CREATE_SUBDIRS         = NO
 
 OUTPUT_LANGUAGE        = English
 
-# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member
+# If the BRIEF_MEMBER_DESC tag is set to YES, 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.
 # The default value is: YES.
 
 BRIEF_MEMBER_DESC      = YES
 
-# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief
+# If the REPEAT_BRIEF tag is set to YES, 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
@@ -127,7 +135,7 @@ ALWAYS_DETAILED_SEC    = NO
 
 INLINE_INHERITED_MEMB  = NO
 
-# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path
+# If the FULL_PATH_NAMES tag is set to YES, 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
 # The default value is: YES.
@@ -197,9 +205,9 @@ MULTILINE_CPP_IS_BRIEF = NO
 
 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.
+# 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.
 # The default value is: NO.
 
 SEPARATE_MEMBER_PAGES  = NO
@@ -261,11 +269,14 @@ OPTIMIZE_OUTPUT_VHDL   = NO
 # extension. Doxygen has a built-in mapping, but you can override or extend it
 # using this tag. The format is ext=language, where ext is a file extension, and
 # language is one of the parsers supported by doxygen: IDL, Java, Javascript,
-# C#, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL. For instance to make
-# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C
-# (default is Fortran), use: inc=Fortran f=C.
+# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:
+# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:
+# Fortran. In the later case the parser tries to guess whether the code is fixed
+# or free formatted code, this is the default for Fortran type files), VHDL. For
+# instance to make doxygen treat .inc files as Fortran files (default is PHP),
+# and .f files as C (default is Fortran), use: inc=Fortran f=C.
 #
-# Note For files without extension you can use no_extension as a placeholder.
+# Note: For files without extension you can use no_extension as a placeholder.
 #
 # Note that for custom extensions you also need to set FILE_PATTERNS otherwise
 # the files are not read by doxygen.
@@ -284,8 +295,8 @@ MARKDOWN_SUPPORT       = YES
 
 # When enabled doxygen tries to link words that correspond to documented
 # classes, or namespaces to their corresponding documentation. Such a link can
-# be prevented in individual cases by by putting a % sign in front of the word
-# or globally by setting AUTOLINK_SUPPORT to NO.
+# be prevented in individual cases by putting a % sign in front of the word or
+# globally by setting AUTOLINK_SUPPORT to NO.
 # The default value is: YES.
 
 AUTOLINK_SUPPORT       = YES
@@ -325,13 +336,20 @@ SIP_SUPPORT            = NO
 IDL_PROPERTY_SUPPORT   = YES
 
 # 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
+# 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.
 # The default value is: NO.
 
 DISTRIBUTE_GROUP_DOC   = NO
 
+# If one adds a struct or class to a group and this option is enabled, then also
+# any nested class or struct is added to the same group. By default this option
+# is disabled and one has to add nested compounds explicitly via \ingroup.
+# The default value is: NO.
+
+GROUP_NESTED_COMPOUNDS = NO
+
 # Set the SUBGROUPING tag to YES 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
@@ -390,7 +408,7 @@ LOOKUP_CACHE_SIZE      = 0
 # Build related configuration options
 #---------------------------------------------------------------------------
 
-# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# 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 respectively EXTRACT_STATIC tags are set to YES.
@@ -400,35 +418,35 @@ LOOKUP_CACHE_SIZE      = 0
 
 EXTRACT_ALL            = YES
 
-# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will
+# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
 # be included in the documentation.
 # The default value is: NO.
 
 EXTRACT_PRIVATE        = NO
 
-# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal
+# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
 # scope will be included in the documentation.
 # The default value is: NO.
 
 EXTRACT_PACKAGE        = NO
 
-# If the EXTRACT_STATIC tag is set to YES all static members of a file will be
+# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
 # included in the documentation.
 # The default value is: NO.
 
 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
+# 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. Does not have any effect
 # for Java sources.
 # The default value is: YES.
 
 EXTRACT_LOCAL_CLASSES  = NO
 
-# This flag is only useful for Objective-C code. When set to YES local methods,
+# This flag is only useful for Objective-C code. If set to YES, local methods,
 # which are defined in the implementation section but not in the interface are
-# included in the documentation. If set to NO only methods in the interface are
+# included in the documentation. If set to NO, only methods in the interface are
 # included.
 # The default value is: NO.
 
@@ -453,21 +471,21 @@ 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 these classes will be included in the various overviews. This option has
-# no effect if EXTRACT_ALL is enabled.
+# to NO, these classes will be included in the various overviews. This option
+# has no effect if EXTRACT_ALL is enabled.
 # The default value is: NO.
 
 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 these declarations will be
+# (class|struct|union) declarations. If set to NO, these declarations will be
 # included in the documentation.
 # The default value is: NO.
 
 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 these
+# documentation blocks found inside the body of a function. If set to NO, these
 # blocks will be appended to the function's detailed documentation block.
 # The default value is: NO.
 
@@ -481,7 +499,7 @@ HIDE_IN_BODY_DOCS      = NO
 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
+# 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.
@@ -490,12 +508,19 @@ INTERNAL_DOCS          = NO
 CASE_SENSE_NAMES       = YES
 
 # If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
-# their full class and namespace scopes in the documentation. If set to YES the
+# their full class and namespace scopes in the documentation. If set to YES, the
 # scope will be hidden.
 # The default value is: NO.
 
 HIDE_SCOPE_NAMES       = YES
 
+# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
+# append additional text to a page's title, such as Class Reference. If set to
+# YES the compound reference will be hidden.
+# The default value is: NO.
+
+HIDE_COMPOUND_REFERENCE= NO
+
 # If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
 # the files that are included by a file in the documentation of that file.
 # The default value is: YES.
@@ -523,14 +548,14 @@ INLINE_INFO            = YES
 
 # If the SORT_MEMBER_DOCS tag is set to YES 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.
+# name. If set to NO, the members will appear in declaration order.
 # The default value is: YES.
 
 SORT_MEMBER_DOCS       = NO
 
 # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
 # descriptions of file, namespace and class members alphabetically by member
-# name. If set to NO the members will appear in declaration order. Note that
+# name. If set to NO, the members will appear in declaration order. Note that
 # this will also influence the order of the classes in the class list.
 # The default value is: NO.
 
@@ -575,27 +600,25 @@ SORT_BY_SCOPE_NAME     = NO
 
 STRICT_PROTO_MATCHING  = 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.
+# 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.
 # The default value is: YES.
 
 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.
+# 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.
 # The default value is: YES.
 
 GENERATE_TESTLIST      = YES
 
-# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug
+# 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.
 # The default value is: YES.
 
 GENERATE_BUGLIST       = YES
 
-# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO)
+# 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.
 # The default value is: YES.
@@ -620,8 +643,8 @@ ENABLED_SECTIONS       =
 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.
+# 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.
 # The default value is: YES.
 
 SHOW_USED_FILES        = YES
@@ -669,8 +692,7 @@ LAYOUT_FILE            =
 # to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.
 # For LaTeX the style of the bibliography can be controlled using
 # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
-# search path. Do not use file names with spaces, bibtex cannot handle them. See
-# also \cite for info how to create references.
+# search path. See also \cite for info how to create references.
 
 CITE_BIB_FILES         =
 
@@ -686,7 +708,7 @@ CITE_BIB_FILES         =
 QUIET                  = YES
 
 # The WARNINGS tag can be used to turn on/off the warning messages that are
-# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES
+# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
 # this implies that the warnings are on.
 #
 # Tip: Turn warnings on while writing the documentation.
@@ -694,7 +716,7 @@ QUIET                  = YES
 
 WARNINGS               = YES
 
-# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate
+# If the WARN_IF_UNDOCUMENTED tag 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.
 # The default value is: YES.
@@ -711,12 +733,18 @@ WARN_IF_DOC_ERROR      = YES
 
 # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
 # are documented, but have no documentation for their parameters or return
-# value. If set to NO doxygen will only warn about wrong or incomplete parameter
-# documentation, but not about the absence of documentation.
+# value. If set to NO, doxygen will only warn about wrong or incomplete
+# parameter documentation, but not about the absence of documentation.
 # The default value is: NO.
 
 WARN_NO_PARAMDOC       = YES
 
+# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
+# a warning is encountered.
+# The default value is: NO.
+
+WARN_AS_ERROR          = 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
@@ -740,7 +768,7 @@ WARN_LOGFILE           =
 # The INPUT tag is 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.
+# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
 # Note: If this tag is empty the current directory is searched.
 
 INPUT                  = .
@@ -756,12 +784,17 @@ INPUT_ENCODING         = UTF-8
 
 # If the value of the INPUT tag contains directories, you can use the
 # FILE_PATTERNS tag to specify one or more wildcard patterns (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, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp,
-# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown,
-# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf,
-# *.qsf, *.as and *.js.
+# *.h) to filter out the source-files in the directories.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# read by doxygen.
+#
+# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
+# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
+# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
+# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f, *.for, *.tcl,
+# *.vhd, *.vhdl, *.ucf, *.qsf, *.as and *.js.
 
 FILE_PATTERNS          = *.c \
                          *.h
@@ -779,7 +812,7 @@ RECURSIVE              = NO
 # Note that relative paths are relative to the directory from which doxygen is
 # run.
 
-EXCLUDE                =
+EXCLUDE                = config.h
 
 # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
 # directories that are symbolic links (a Unix file system feature) are excluded
@@ -795,10 +828,7 @@ EXCLUDE_SYMLINKS       = NO
 # 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       = *.cmdline.* \
-                         gcc-compat.h \
-                         *.command_list.h \
-                         *.completion.h
+EXCLUDE_PATTERNS       = gcc-compat.h
 
 # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
 # (namespaces, classes, functions, etc.) that should be excluded from the
@@ -851,6 +881,10 @@ IMAGE_PATH             =
 # Note that the filter must not add or remove lines; it is applied before the
 # code is scanned, but not when the output code is generated. If lines are added
 # or removed, the anchors will not be placed correctly.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
 
 INPUT_FILTER           =
 
@@ -860,11 +894,15 @@ INPUT_FILTER           =
 # (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
 # filters are used. If the FILTER_PATTERNS tag is empty or if none of the
 # patterns match the file name, INPUT_FILTER is applied.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
 
 FILTER_PATTERNS        =
 
 # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
-# INPUT_FILTER ) will also be used to filter the input files that are used for
+# INPUT_FILTER) will also be used to filter the input files that are used for
 # producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
 # The default value is: NO.
 
@@ -896,7 +934,7 @@ USE_MDFILE_AS_MAINPAGE =
 # also VERBATIM_HEADERS is set to NO.
 # The default value is: NO.
 
-SOURCE_BROWSER         = YES
+SOURCE_BROWSER         = NO
 
 # Setting the INLINE_SOURCES tag to YES will include the body of functions,
 # classes and enums directly into the documentation.
@@ -924,7 +962,7 @@ REFERENCED_BY_RELATION = YES
 REFERENCES_RELATION    = YES
 
 # If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
-# to YES, then the hyperlinks from functions in REFERENCES_RELATION and
+# to YES then the hyperlinks from functions in REFERENCES_RELATION and
 # REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
 # link to the documentation.
 # The default value is: YES.
@@ -961,7 +999,7 @@ SOURCE_TOOLTIPS        = YES
 # The default value is: NO.
 # This tag requires that the tag SOURCE_BROWSER is set to YES.
 
-USE_HTAGS              = YES
+USE_HTAGS              = NO
 
 # If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
 # verbatim copy of the header file for each class for which an include is
@@ -969,7 +1007,7 @@ USE_HTAGS              = YES
 # See also: Section \class.
 # The default value is: YES.
 
-VERBATIM_HEADERS       = YES
+VERBATIM_HEADERS       = NO
 
 #---------------------------------------------------------------------------
 # Configuration options related to the alphabetical class index
@@ -1001,7 +1039,7 @@ IGNORE_PREFIX          =
 # Configuration options related to the HTML output
 #---------------------------------------------------------------------------
 
-# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output
+# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
 # The default value is: YES.
 
 GENERATE_HTML          = YES
@@ -1063,13 +1101,15 @@ HTML_FOOTER            = web/footer.html
 
 HTML_STYLESHEET        =
 
-# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user-
-# defined cascading style sheet that is included after the standard style sheets
+# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# cascading style sheets that are included after the standard style sheets
 # created by doxygen. Using this option one can overrule certain style aspects.
 # This is preferred over using HTML_STYLESHEET since it does not replace the
-# standard style sheet and is therefor more robust against future updates.
-# Doxygen will copy the style sheet file to the output directory. For an example
-# see the documentation.
+# standard style sheet and is therefore more robust against future updates.
+# Doxygen will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list). For an example see the documentation.
 # This tag requires that the tag GENERATE_HTML is set to YES.
 
 HTML_EXTRA_STYLESHEET  =
@@ -1085,7 +1125,7 @@ HTML_EXTRA_STYLESHEET  =
 HTML_EXTRA_FILES       =
 
 # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
-# will adjust the colors in the stylesheet and background images according to
+# will adjust the colors in the style sheet and background images according to
 # this color. Hue is specified as an angle on a colorwheel, see
 # http://en.wikipedia.org/wiki/Hue for more information. For instance the value
 # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
@@ -1116,8 +1156,9 @@ HTML_COLORSTYLE_GAMMA  = 80
 
 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
 # page will contain the date and time when the page was generated. Setting this
-# to NO can help when comparing the output of multiple runs.
-# The default value is: YES.
+# to YES can help to show when doxygen was last run and thus if the
+# documentation is up to date.
+# The default value is: NO.
 # This tag requires that the tag GENERATE_HTML is set to YES.
 
 HTML_TIMESTAMP         = YES
@@ -1213,28 +1254,29 @@ GENERATE_HTMLHELP      = NO
 CHM_FILE               =
 
 # 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
+# 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.
 # The file has to be specified with full path.
 # This tag requires that the tag GENERATE_HTMLHELP is set to YES.
 
 HHC_LOCATION           =
 
-# 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).
+# 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).
 # The default value is: NO.
 # This tag requires that the tag GENERATE_HTMLHELP is set to YES.
 
 GENERATE_CHI           = NO
 
-# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc)
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
 # and project file content.
 # This tag requires that the tag GENERATE_HTMLHELP is set to YES.
 
 CHM_INDEX_ENCODING     =
 
-# 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.
+# 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. Furthermore it
+# enables the Previous and Next buttons.
 # The default value is: NO.
 # This tag requires that the tag GENERATE_HTMLHELP is set to YES.
 
@@ -1347,7 +1389,7 @@ DISABLE_INDEX          = NO
 # 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
 # (i.e. any modern browser). Windows users are probably better off using the
-# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can
+# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
 # further fine-tune the look of the index. As an example, the default style
 # sheet generated by doxygen has an example that shows how to put an image at
 # the root of the tree instead of the PROJECT_NAME. Since the tree basically has
@@ -1375,7 +1417,7 @@ ENUM_VALUES_PER_LINE   = 4
 
 TREEVIEW_WIDTH         = 250
 
-# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to
+# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
 # external symbols imported via tag files in a separate window.
 # The default value is: NO.
 # This tag requires that the tag GENERATE_HTML is set to YES.
@@ -1404,7 +1446,7 @@ FORMULA_TRANSPARENT    = YES
 
 # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
 # http://www.mathjax.org) which uses client side Javascript for the rendering
-# instead of using prerendered bitmaps. Use this if you do not have LaTeX
+# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
 # installed or if you want to formulas look prettier in the HTML output. When
 # enabled you may also need to install MathJax separately and configure the path
 # to it using the MATHJAX_RELPATH option.
@@ -1474,11 +1516,11 @@ SEARCHENGINE           = NO
 
 # When the SERVER_BASED_SEARCH tag is enabled the search engine will be
 # implemented using a web server instead of a web client using Javascript. There
-# are two flavours of web server based searching depending on the
-# EXTERNAL_SEARCH setting. When disabled, doxygen will generate a PHP script for
-# searching and an index file used by the script. When EXTERNAL_SEARCH is
-# enabled the indexing and searching needs to be provided by external tools. See
-# the section "External Indexing and Searching" for details.
+# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
+# setting. When disabled, doxygen will generate a PHP script for searching and
+# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
+# and searching needs to be provided by external tools. See the section
+# "External Indexing and Searching" for details.
 # The default value is: NO.
 # This tag requires that the tag SEARCHENGINE is set to YES.
 
@@ -1490,7 +1532,7 @@ SERVER_BASED_SEARCH    = NO
 # external search engine pointed to by the SEARCHENGINE_URL option to obtain the
 # search results.
 #
-# Doxygen ships with an example indexer ( doxyindexer) and search engine
+# Doxygen ships with an example indexer (doxyindexer) and search engine
 # (doxysearch.cgi) which are based on the open source search engine library
 # Xapian (see: http://xapian.org/).
 #
@@ -1503,7 +1545,7 @@ EXTERNAL_SEARCH        = NO
 # The SEARCHENGINE_URL should point to a search engine hosted by a web server
 # which will return the search results when EXTERNAL_SEARCH is enabled.
 #
-# Doxygen ships with an example indexer ( doxyindexer) and search engine
+# Doxygen ships with an example indexer (doxyindexer) and search engine
 # (doxysearch.cgi) which are based on the open source search engine library
 # Xapian (see: http://xapian.org/). See the section "External Indexing and
 # Searching" for details.
@@ -1541,7 +1583,7 @@ EXTRA_SEARCH_MAPPINGS  =
 # Configuration options related to the LaTeX output
 #---------------------------------------------------------------------------
 
-# If the GENERATE_LATEX tag is set to YES doxygen will generate LaTeX output.
+# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
 # The default value is: YES.
 
 GENERATE_LATEX         = NO
@@ -1572,7 +1614,7 @@ LATEX_CMD_NAME         = latex
 
 MAKEINDEX_CMD_NAME     = makeindex
 
-# If the COMPACT_LATEX tag is set to YES doxygen generates more compact LaTeX
+# 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.
 # The default value is: NO.
@@ -1590,9 +1632,12 @@ COMPACT_LATEX          = NO
 PAPER_TYPE             = a4wide
 
 # The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
-# that should be included in the LaTeX output. To get the times font for
-# instance you can specify
-# EXTRA_PACKAGES=times
+# that should be included in the LaTeX output. The package can be specified just
+# by its name or with the correct syntax as to be used with the LaTeX
+# \usepackage command. To get the times font for instance you can specify :
+# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
+# To use the option intlimits with the amsmath package you can specify:
+# EXTRA_PACKAGES=[intlimits]{amsmath}
 # If left blank no extra packages will be included.
 # This tag requires that the tag GENERATE_LATEX is set to YES.
 
@@ -1606,23 +1651,36 @@ EXTRA_PACKAGES         =
 #
 # Note: Only use a user-defined header if you know what you are doing! The
 # following commands have a special meaning inside the header: $title,
-# $datetime, $date, $doxygenversion, $projectname, $projectnumber. Doxygen will
-# replace them by respectively the title of the page, the current date and time,
-# only the current date, the version number of doxygen, the project name (see
-# PROJECT_NAME), or the project number (see PROJECT_NUMBER).
+# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
+# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
+# string, for the replacement values of the other commands the user is referred
+# to HTML_HEADER.
 # This tag requires that the tag GENERATE_LATEX is set to YES.
 
 LATEX_HEADER           =
 
 # The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
 # generated LaTeX document. The footer should contain everything after the last
-# chapter. If it is left blank doxygen will generate a standard footer.
+# chapter. If it is left blank doxygen will generate a standard footer. See
+# LATEX_HEADER for more information on how to generate a default footer and what
+# special commands can be used inside the footer.
 #
 # Note: Only use a user-defined footer if you know what you are doing!
 # This tag requires that the tag GENERATE_LATEX is set to YES.
 
 LATEX_FOOTER           =
 
+# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# LaTeX style sheets that are included after the standard style sheets created
+# by doxygen. Using this option one can overrule certain style aspects. Doxygen
+# will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list).
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_STYLESHEET =
+
 # The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
 # other source files which should be copied to the LATEX_OUTPUT output
 # directory. Note that the files will be copied as-is; there are no commands or
@@ -1640,8 +1698,8 @@ LATEX_EXTRA_FILES      =
 
 PDF_HYPERLINKS         = NO
 
-# If the LATEX_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
-# the PDF file directly from the LaTeX files. Set this option to YES to get a
+# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
+# the PDF file directly from the LaTeX files. Set this option to YES, to get a
 # higher quality PDF documentation.
 # The default value is: YES.
 # This tag requires that the tag GENERATE_LATEX is set to YES.
@@ -1682,11 +1740,19 @@ LATEX_SOURCE_CODE      = NO
 
 LATEX_BIB_STYLE        = plain
 
+# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_TIMESTAMP        = NO
+
 #---------------------------------------------------------------------------
 # Configuration options related to the RTF output
 #---------------------------------------------------------------------------
 
-# If the GENERATE_RTF tag is set to YES doxygen will generate RTF output. The
+# 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 too pretty with other RTF
 # readers/editors.
 # The default value is: NO.
@@ -1701,7 +1767,7 @@ GENERATE_RTF           = NO
 
 RTF_OUTPUT             = rtf
 
-# If the COMPACT_RTF tag is set to YES doxygen generates more compact 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.
 # The default value is: NO.
@@ -1738,11 +1804,21 @@ RTF_STYLESHEET_FILE    =
 
 RTF_EXTENSIONS_FILE    =
 
+# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code
+# with syntax highlighting in the RTF output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_SOURCE_CODE        = NO
+
 #---------------------------------------------------------------------------
 # Configuration options related to the man page output
 #---------------------------------------------------------------------------
 
-# If the GENERATE_MAN tag is set to YES doxygen will generate man pages for
+# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
 # classes and files.
 # The default value is: NO.
 
@@ -1766,6 +1842,13 @@ MAN_OUTPUT             = man
 
 MAN_EXTENSION          = .3
 
+# The MAN_SUBDIR tag determines the name of the directory created within
+# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
+# MAN_EXTENSION with the initial . removed.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_SUBDIR             =
+
 # 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
@@ -1779,7 +1862,7 @@ 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
+# 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.
 # The default value is: NO.
 
@@ -1793,19 +1876,7 @@ GENERATE_XML           = NO
 
 XML_OUTPUT             = xml
 
-# The XML_SCHEMA tag can be used to specify a XML schema, which can be used by a
-# validating XML parser to check the syntax of the XML files.
-# This tag requires that the tag GENERATE_XML is set to YES.
-
-XML_SCHEMA             =
-
-# The XML_DTD tag can be used to specify a XML DTD, which can be used by a
-# validating XML parser to check the syntax of the XML files.
-# This tag requires that the tag GENERATE_XML is set to YES.
-
-XML_DTD                =
-
-# If the XML_PROGRAMLISTING tag is set to YES doxygen will dump the program
+# 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.
@@ -1818,7 +1889,7 @@ XML_PROGRAMLISTING     = YES
 # Configuration options related to the DOCBOOK output
 #---------------------------------------------------------------------------
 
-# If the GENERATE_DOCBOOK tag is set to YES doxygen will generate Docbook files
+# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
 # that can be used to generate PDF.
 # The default value is: NO.
 
@@ -1832,14 +1903,23 @@ GENERATE_DOCBOOK       = NO
 
 DOCBOOK_OUTPUT         = docbook
 
+# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the
+# program listings (including syntax highlighting and cross-referencing
+# information) to the DOCBOOK output. Note that enabling this will significantly
+# increase the size of the DOCBOOK output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_PROGRAMLISTING = NO
+
 #---------------------------------------------------------------------------
 # Configuration options for the AutoGen Definitions output
 #---------------------------------------------------------------------------
 
-# If the GENERATE_AUTOGEN_DEF tag is set to YES doxygen will generate an AutoGen
-# Definitions (see http://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.
+# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
+# AutoGen Definitions (see http://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.
 # The default value is: NO.
 
 GENERATE_AUTOGEN_DEF   = NO
@@ -1848,7 +1928,7 @@ 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
+# 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.
@@ -1856,7 +1936,7 @@ GENERATE_AUTOGEN_DEF   = NO
 
 GENERATE_PERLMOD       = NO
 
-# If the PERLMOD_LATEX tag is set to YES doxygen will generate the necessary
+# 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.
 # The default value is: NO.
@@ -1864,9 +1944,9 @@ GENERATE_PERLMOD       = NO
 
 PERLMOD_LATEX          = NO
 
-# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be nicely
+# 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
+# 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.
 # The default value is: YES.
@@ -1886,14 +1966,14 @@ PERLMOD_MAKEVAR_PREFIX =
 # Configuration options related to the preprocessor
 #---------------------------------------------------------------------------
 
-# If the ENABLE_PREPROCESSING tag is set to YES doxygen will evaluate all
+# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
 # C-preprocessor directives found in the sources and include files.
 # The default value is: YES.
 
 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 only conditional compilation will be
+# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
+# in the source code. If set to NO, only conditional compilation will be
 # performed. Macro expansion can be done in a controlled way by setting
 # EXPAND_ONLY_PREDEF to YES.
 # The default value is: NO.
@@ -1909,7 +1989,7 @@ MACRO_EXPANSION        = YES
 
 EXPAND_ONLY_PREDEF     = NO
 
-# If the SEARCH_INCLUDES tag is set to YES the includes files in the
+# If the SEARCH_INCLUDES tag is set to YES, the include files in the
 # INCLUDE_PATH will be searched if a #include is found.
 # The default value is: YES.
 # This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
@@ -1952,9 +2032,9 @@ PREDEFINED             = __GNUC__=4 \
 EXPAND_AS_DEFINED      =
 
 # If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
-# remove all refrences to 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
+# remove all references to 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.
 # The default value is: YES.
 # This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
@@ -1974,7 +2054,7 @@ SKIP_FUNCTION_MACROS   = YES
 # where loc1 and loc2 can be relative or absolute paths or URLs. See the
 # section "Linking to external documentation" for more information about the use
 # of tag files.
-# Note: Each tag file must have an unique name (where the name does NOT include
+# Note: 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.
 
@@ -1986,20 +2066,21 @@ TAGFILES               =
 
 GENERATE_TAGFILE       =
 
-# If the ALLEXTERNALS tag is set to YES all external class will be listed in the
-# class index. If set to NO only the inherited external classes will be listed.
+# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
+# the class index. If set to NO, only the inherited external classes will be
+# listed.
 # The default value is: NO.
 
 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
+# 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.
 # The default value is: YES.
 
 EXTERNAL_GROUPS        = YES
 
-# If the EXTERNAL_PAGES tag is set to YES all external pages will be listed in
+# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
 # the related pages index. If set to NO, only the current project's pages will
 # be listed.
 # The default value is: YES.
@@ -2016,7 +2097,7 @@ PERL_PATH              = /usr/bin/perl
 # Configuration options related to the dot tool
 #---------------------------------------------------------------------------
 
-# If the CLASS_DIAGRAMS tag is set to YES doxygen will generate a class diagram
+# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
 # (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
 # NO turns the diagrams off. Note that this option also works with HAVE_DOT
 # disabled, but it is recommended to install and use dot, since it yields more
@@ -2041,7 +2122,7 @@ MSCGEN_PATH            =
 
 DIA_PATH               =
 
-# If set to YES, the inheritance and collaboration graphs will hide inheritance
+# 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.
 # The default value is: YES.
 
@@ -2066,7 +2147,7 @@ HAVE_DOT               = NO
 
 DOT_NUM_THREADS        = 0
 
-# When you want a differently looking font n the dot files that doxygen
+# When you want a differently looking font in the dot files that doxygen
 # generates you can specify the font name using DOT_FONTNAME. You need to make
 # sure dot is able to find the font, which can be done by putting it in a
 # standard location or by setting the DOTFONTPATH environment variable or by
@@ -2114,7 +2195,7 @@ COLLABORATION_GRAPH    = YES
 
 GROUP_GRAPHS           = YES
 
-# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# 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.
 # The default value is: NO.
@@ -2166,7 +2247,8 @@ INCLUDED_BY_GRAPH      = YES
 #
 # 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.
+# functions only using the \callgraph command. Disabling a call graph can be
+# accomplished by means of the command \hidecallgraph.
 # The default value is: NO.
 # This tag requires that the tag HAVE_DOT is set to YES.
 
@@ -2177,7 +2259,8 @@ CALL_GRAPH             = NO
 #
 # Note that enabling this option will significantly increase the time of a run.
 # So in most cases it will be better to enable caller graphs for selected
-# functions only using the \callergraph command.
+# functions only using the \callergraph command. Disabling a caller graph can be
+# accomplished by means of the command \hidecallergraph.
 # The default value is: NO.
 # This tag requires that the tag HAVE_DOT is set to YES.
 
@@ -2200,11 +2283,15 @@ GRAPHICAL_HIERARCHY    = YES
 DIRECTORY_GRAPH        = YES
 
 # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
-# generated by dot.
+# generated by dot. For an explanation of the image formats see the section
+# output formats in the documentation of the dot tool (Graphviz (see:
+# http://www.graphviz.org/)).
 # Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
 # to make the SVG files visible in IE 9+ (other browsers do not have this
 # requirement).
-# Possible values are: png, jpg, gif and svg.
+# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,
+# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
+# png:gdiplus:gdiplus.
 # The default value is: png.
 # This tag requires that the tag HAVE_DOT is set to YES.
 
@@ -2247,6 +2334,19 @@ MSCFILE_DIRS           =
 
 DIAFILE_DIRS           =
 
+# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
+# path where java can find the plantuml.jar file. If left blank, it is assumed
+# PlantUML is not used or called during a preprocessing step. Doxygen will
+# generate a warning when it encounters a \startuml command in this case and
+# will not generate output for the diagram.
+
+PLANTUML_JAR_PATH      =
+
+# When using plantuml, the specified paths are searched for files specified by
+# the !include statement in a plantuml block.
+
+PLANTUML_INCLUDE_PATH  =
+
 # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
 # that will be shown in the graph. If the number of nodes in a graph becomes
 # larger than this value, doxygen will truncate the graph, which is visualized
@@ -2283,7 +2383,7 @@ MAX_DOT_GRAPH_DEPTH    = 0
 
 DOT_TRANSPARENT        = NO
 
-# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# Set the DOT_MULTI_TARGETS tag to YES to 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.
@@ -2300,7 +2400,7 @@ DOT_MULTI_TARGETS      = NO
 
 GENERATE_LEGEND        = YES
 
-# If the DOT_CLEANUP tag is set to YES doxygen will remove the intermediate dot
+# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot
 # files that are used to generate the various graphs.
 # The default value is: YES.
 # This tag requires that the tag HAVE_DOT is set to YES.
diff --git a/INSTALL b/INSTALL
index 85b4fab..45676b7 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -1,5 +1,11 @@
 Any knowledge of how to work with mouse and icons is not required.
 
+Installing lopsub
+~~~~~~~~~~~~~~~~~
+       git clone git://git.tuebingen.mpg.de/lopsub
+       cd lopsub && make && sudo make install
+       (see http://people.tuebingen.mpg.de/maan/lopsub/)
+
 Installing osl
 ~~~~~~~~~~~~~~
        git clone git://git.tuebingen.mpg.de/osl
@@ -12,14 +18,12 @@ Installing paraslash from tarball
 
 Installing paraslash from git
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-       autoconf && autoheader && make && sudo make install
+       autoconf && autoheader && ./configure && make && sudo make install
 
 Example for cross-compiling
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
-       export CROSS_COMPILE='armv6j-hardfloat-linux-gnueabi-'
+       export CC='armv6j-hardfloat-linux-gnueabi-gcc'
        export PATH="/usr/cross/arm/bin:$PATH"
-       export CC=${CROSS_COMPILE}gcc
-
        export LDFLAGS='
                -L/usr/sysroot/arm/lib
                -L/usr/sysroot/arm/usr/lib
@@ -29,7 +33,7 @@ Example for cross-compiling
        autoconf
        autoheader
        ./configure --host=arm-linux-gnueabi --prefix /usr/sysroot/arm/usr/local
-       make CROSS_COMPILE=$CROSS_COMPILE
+       make
 
 For details see the user manual:
 
index ec55c8e..d4a83a7 100644 (file)
@@ -8,17 +8,12 @@ datarootdir := @datarootdir@
 PACKAGE_TARNAME := @PACKAGE_TARNAME@
 PACKAGE_VERSION := @PACKAGE_VERSION@
 
-INSTALL := @INSTALL@
+FLEX := @FLEX@
+BISON := @BISON@
 M4 := @M4@
-GENGETOPT := @GENGETOPT@
-HELP2MAN := @HELP2MAN@
-
-ggo_descriptions_declared := @ggo_descriptions_declared@
+LOPSUBGEN := @LOPSUBGEN@
 
 executables := @executables@
-receivers := @receivers@
-filters := @filters@
-writers := @writers@
 
 recv_objs := @recv_objs@
 filter_objs := @filter_objs@
@@ -26,7 +21,7 @@ client_objs := @client_objs@
 gui_objs := @gui_objs@
 audiod_objs := @audiod_objs@
 audioc_objs := @audioc_objs@
-fade_objs := @fade_objs@
+mixer_objs := @mixer_objs@
 server_objs := @server_objs@
 write_objs := @write_objs@
 afh_objs := @afh_objs@
@@ -39,6 +34,7 @@ osl_cppflags := @osl_cppflags@
 id3tag_cppflags := @id3tag_cppflags@
 openssl_cppflags := @openssl_cppflags@
 gcrypt_cppflags := @gcrypt_cppflags@
+lopsub_cppflags := @lopsub_cppflags@
 ogg_cppflags := @ogg_cppflags@
 mad_cppflags := @mad_cppflags@
 faad_cppflags := @faad_cppflags@
@@ -50,9 +46,7 @@ samplerate_cppflags := @samplerate_cppflags@
 readline_cppflags := @readline_cppflags@
 alsa_cppflags := @alsa_cppflags@
 oss_cppflags := @oss_cppflags@
-mp4v2_cppflags := @mp4v2_cppflags@
 
-clock_gettime_ldflags := @clock_gettime_ldflags@
 id3tag_ldflags := @id3tag_ldflags@
 ogg_ldflags := @ogg_ldflags@
 vorbis_ldflags := @vorbis_ldflags@
@@ -61,6 +55,7 @@ opus_ldflags := @opus_ldflags@
 faad_ldflags := @faad_ldflags@
 mad_ldflags := @mad_ldflags@
 flac_ldflags := @flac_ldflags@
+lopsub_ldflags := @lopsub_ldflags@
 oss_ldflags := @oss_ldflags@
 alsa_ldflags := @alsa_ldflags@
 pthread_ldflags := @pthread_ldflags@
@@ -69,9 +64,7 @@ readline_ldflags := @readline_ldflags@
 samplerate_ldflags := @samplerate_ldflags@
 osl_ldflags := @osl_ldflags@
 curses_ldflags := @curses_ldflags@
-core_audio_ldflags := @core_audio_ldflags@
 crypto_ldflags := @crypto_ldflags@
 iconv_ldflags := @iconv_ldflags@
-mp4v2_ldflags := @mp4v2_ldflags@
 
 include Makefile.real
index 8ededf6..b88de22 100644 (file)
@@ -6,10 +6,12 @@ MAKEFLAGS += -Rr
 ifeq ("$(origin CC)", "default")
         CC := cc
 endif
+.ONESHELL:
+.SHELLFLAGS := -ec
 
+LOGLEVELS := LL_DEBUG,LL_INFO,LL_NOTICE,LL_WARNING,LL_ERROR,LL_CRIT,LL_EMERG
 vardir := /var/paraslash
 mandir := $(datarootdir)/man/man1
-STRIP := $(CROSS_COMPILE)strip
 MKDIR_P := mkdir -p
 prefixed_executables := $(addprefix para_, $(executables))
 
@@ -18,30 +20,53 @@ uname_s := $(shell uname -s 2>/dev/null || echo "UNKNOWN_OS")
 uname_rs := $(shell uname -rs)
 cc_version := $(shell $(CC) --version | head -n 1)
 GIT_VERSION := $(shell ./GIT-VERSION-GEN git-version.h)
-COPYRIGHT_YEAR := 2017
+COPYRIGHT_YEAR := 2021
 
 ifeq ("$(origin O)", "command line")
        build_dir := $(O)
 else
        build_dir := build
 endif
-ggo_dir := $(build_dir)/ggo
 object_dir := $(build_dir)/objects
 dep_dir := $(build_dir)/deps
 man_dir := $(build_dir)/man/man1
-cmdline_dir := $(build_dir)/cmdline
-cmdlist_dir := $(build_dir)/cmdlist
 m4depdir := $(build_dir)/m4deps
-help2man_dir := $(build_dir)/help2man
-m4_ggo_dir := m4/gengetopt
+lls_suite_dir := $(build_dir)/lls
+lls_m4_dir := m4/lls
 test_dir := t
+yy_src_dir = yy
+yy_build_dir = $(build_dir)/yy
 
 # sort removes duplicate words, which is all we need here
 all_objs := $(sort $(recv_objs) $(filter_objs) $(client_objs) $(gui_objs) \
-       $(audiod_objs) $(audioc_objs) $(fade_objs) $(server_objs) \
+       $(audiod_objs) $(audioc_objs) $(mixer_objs) $(server_objs) \
        $(write_objs) $(afh_objs) $(play_objs))
-deps := $(addprefix $(dep_dir)/, $(filter-out %.cmdline.d, $(all_objs:.o=.d)))
-m4_deps := $(addprefix $(m4depdir)/, $(addsuffix .m4d, $(executables)))
+deps := $(addprefix $(dep_dir)/, $(all_objs:.o=.d))
+deps += $(addprefix $(dep_dir)/, mp.bison.d mp.flex.d)
+
+afh_objs += afh.lsg.o
+audioc_objs += audioc.lsg.o
+audiod_objs += $(addsuffix _cmd.lsg.o, recv filter audiod write) \
+       client.lsg.o audiod.lsg.o
+client_objs += client.lsg.o
+mixer_objs += mixer.lsg.o
+filter_objs += filter_cmd.lsg.o filter.lsg.o
+gui_objs += gui.lsg.o
+play_objs += $(addsuffix _cmd.lsg.o, recv filter play write) play.lsg.o
+recv_objs += recv_cmd.lsg.o recv.lsg.o
+server_objs += server_cmd.lsg.o server.lsg.o
+write_objs += write_cmd.lsg.o write.lsg.o
+
+cmd_suites := $(addsuffix _cmd, audiod server play recv filter write)
+suites := $(addprefix $(lls_suite_dir)/, $(cmd_suites) $(executables))
+m4_lls_deps := $(addsuffix .m4d, $(suites))
+lsg_h := $(addsuffix .lsg.h, $(suites))
+
+# flex/bison objects and headers are only needed if para_server is built
+ifeq ("$(findstring server, $(executables))", "server")
+       server_objs += mp.flex.o mp.bison.o
+       yy_h := $(yy_build_dir)/mp.bison.h
+endif
 
 # now prefix all objects with object dir
 recv_objs := $(addprefix $(object_dir)/, $(recv_objs))
@@ -50,7 +75,7 @@ client_objs := $(addprefix $(object_dir)/, $(client_objs))
 gui_objs := $(addprefix $(object_dir)/, $(gui_objs))
 audiod_objs := $(addprefix $(object_dir)/, $(audiod_objs))
 audioc_objs := $(addprefix $(object_dir)/, $(audioc_objs))
-fade_objs := $(addprefix $(object_dir)/, $(fade_objs))
+mixer_objs := $(addprefix $(object_dir)/, $(mixer_objs))
 server_objs := $(addprefix $(object_dir)/, $(server_objs))
 write_objs := $(addprefix $(object_dir)/, $(write_objs))
 afh_objs := $(addprefix $(object_dir)/, $(afh_objs))
@@ -61,59 +86,48 @@ man_pages := $(patsubst %, $(man_dir)/%.1, $(prefixed_executables))
 autocrap := config.h.in configure
 tarball_pfx := $(PACKAGE_TARNAME)-$(GIT_VERSION)
 tarball_delete := $(addprefix $(tarball_pfx)/, web .gitignore)
-tarball := $(tarball_pfx).tar.bz2
+tarball := $(tarball_pfx).tar.xz
 
-.PHONY: all clean clean2 distclean maintainer-clean install man tarball
 all: $(prefixed_executables) $(man_pages)
+.PHONY: all mostlyclean clean distclean maintainer-clean install \
+       install-strip man dist tarball
+
 man: $(man_pages)
-tarball: $(tarball)
 
-include $(m4_ggo_dir)/makefile
+include $(lls_m4_dir)/makefile
 include $(test_dir)/makefile.test
+include $(yy_src_dir)/makefile
 ifeq ($(findstring clean, $(MAKECMDGOALS)),)
 -include $(deps)
--include $(m4_deps)
+-include $(m4_lls_deps)
 endif
 
-$(object_dir) $(man_dir) $(ggo_dir) $(cmdline_dir) $(dep_dir) $(m4depdir) \
-               $(help2man_dir) $(cmdlist_dir):
-       $(Q) $(MKDIR_P) $@
-
-# When in doubt, use brute force (Ken Thompson)
-TOUPPER = \
-$(subst a,A,$(subst b,B,$(subst c,C,$(subst d,D,$(subst e,E,\
-$(subst f,F,$(subst g,G,$(subst h,H,$(subst i,I,$(subst j,J,\
-$(subst k,K,$(subst l,L,$(subst m,M,$(subst n,N,$(subst o,O,\
-$(subst p,P,$(subst q,Q,$(subst r,R,$(subst s,S,$(subst t,T,\
-$(subst u,U,$(subst v,V,$(subst w,W,$(subst x,X,$(subst y,Y,\
-$(subst z,Z,$1))))))))))))))))))))))))))
+$(object_dir) $(man_dir) $(dep_dir) $(m4depdir) $(lls_suite_dir) \
+       $(yy_build_dir):
+       @$(MKDIR_P) $@
 
 CPPFLAGS += -DBINDIR='"$(bindir)"'
 CPPFLAGS += -DCOPYRIGHT_YEAR='"$(COPYRIGHT_YEAR)"'
 CPPFLAGS += -DBUILD_DATE='"$(build_date)"'
+CPPFLAGS += -DLOGLEVELS='$(LOGLEVELS)'
 CPPFLAGS += -DUNAME_RS='"$(uname_rs)"'
 CPPFLAGS += -DCC_VERSION='"$(cc_version)"'
-CPPFLAGS += -I/usr/local/include
-CPPFLAGS += -I$(cmdline_dir)
-CPPFLAGS += -I$(cmdlist_dir)
-
-CFLAGS += -Os
-CFLAGS += -Wuninitialized
-CFLAGS += -Wchar-subscripts
-CFLAGS += -Werror-implicit-function-declaration
-CFLAGS += -Wmissing-noreturn
-CFLAGS += -Wbad-function-cast
-CFLAGS += -fno-strict-aliasing
-
-STRICT_CFLAGS = $(CFLAGS)
-STRICT_CFLAGS += -g -Wundef -W
+CPPFLAGS += -I$(lls_suite_dir)
+CPPFLAGS += -I$(yy_build_dir)
+CPPFLAGS += $(lopsub_cppflags)
+
+STRICT_CFLAGS += -fno-strict-aliasing
+STRICT_CFLAGS += -g
+STRICT_CFLAGS += -Os
+STRICT_CFLAGS += -Wundef -W -Wuninitialized
+STRICT_CFLAGS += -Wchar-subscripts
+STRICT_CFLAGS += -Werror-implicit-function-declaration
+STRICT_CFLAGS += -Wmissing-noreturn
+STRICT_CFLAGS += -Wbad-function-cast
 STRICT_CFLAGS += -Wredundant-decls
 STRICT_CFLAGS += -Wno-sign-compare -Wno-unknown-pragmas
-STRICT_CFLAGS += -Wformat -Wformat-security
-STRICT_CFLAGS += -Wmissing-format-attribute
 STRICT_CFLAGS += -Wdeclaration-after-statement
-
-LDFLAGS += $(clock_gettime_ldflags)
+STRICT_CFLAGS += -Wformat -Wformat-security -Wmissing-format-attribute
 
 ifeq ($(uname_s),Linux)
        # these cause warnings on *BSD
@@ -131,158 +145,117 @@ cc-option = $(shell \
 )
 
 STRICT_CFLAGS += $(call cc-option, -Wformat-signedness)
+STRICT_CFLAGS += $(call cc-option, -Wdiscarded-qualifiers)
 
 # To put more focus on warnings, be less verbose as default
 # Use 'make V=1' to see the full commands
 ifeq ("$(origin V)", "command line")
-       Q :=
+       SAY =
 else
-       Q := @
+       SAY = @echo '$(strip $(1))'
 endif
 
-$(cmdlist_dir)/%.command_list.h: %.cmd %.c | $(cmdlist_dir)
-       @[ -z "$(Q)" ] || echo 'GEN $@'
-       $(Q) ./command_util.bash h < $< >$@
-$(cmdlist_dir)/%.command_list.man: %.cmd %.c | $(cmdlist_dir)
-       @[ -z "$(Q)" ] || echo 'GEN $@'
-       $(Q) ./command_util.bash man < $< > $@
-$(cmdlist_dir)/%.completion.h: %.cmd | $(cmdlist_dir)
-       @[ -z "$(Q)" ] || echo 'GEN $@'
-       $(Q) ./command_util.bash compl $(strip $(call TOUPPER,$(*F)))_COMPLETERS \
-               $(strip $(call TOUPPER,$(*F)))_COMMANDS < $< > $@
-
-$(cmdlist_dir)/server.command_list.h \
-$(cmdlist_dir)/server.command_list.man \
-$(cmdlist_dir)/server.completion.h \
-: command.c
-
-$(cmdlist_dir)/afs.command_list.h \
-$(cmdlist_dir)/afs.command_list.man \
-$(cmdlist_dir)/afs.completion.h \
-: afs.c aft.c attribute.c
-
-$(cmdlist_dir)/audiod.command_list.h \
-$(cmdlist_dir)/audiod.command_list.man \
-$(cmdlist_dir)/audiod.completion.h \
-: audiod_command.c
-
-server_command_lists := $(cmdlist_dir)/server.command_list.man \
-       $(cmdlist_dir)/afs.command_list.man
-audiod_command_lists := $(cmdlist_dir)/audiod.command_list.man
-play_command_lists := $(cmdlist_dir)/play.command_list.man
-
-$(man_dir)/para_server.1: $(server_command_lists)
-$(man_dir)/para_audiod.1: $(audiod_command_lists)
-$(man_dir)/para_play.1: $(play_command_lists)
-
-$(man_dir)/para_server.1: man_util_command_lists := $(server_command_lists)
-$(man_dir)/para_audiod.1: man_util_command_lists := $(audiod_command_lists)
-$(man_dir)/para_play.1: man_util_command_lists := $(play_command_lists)
-
-$(man_dir)/para_%.1: $(ggo_dir)/%.ggo man_util.bash \
-               git-version.h | $(man_dir) $(help2man_dir)
-       @[ -z "$(Q)" ] || echo 'MAN $<'
-       $(Q) \
-               COMMAND_LISTS="$(man_util_command_lists)" \
-               FILTERS="$(filters)" \
-               GENGETOPT=$(GENGETOPT) \
-               GGO_DIR=$(ggo_dir) \
-               HELP2MAN=$(HELP2MAN) \
-               HELP2MAN_DIR=$(help2man_dir) \
-               RECEIVERS="$(receivers)" \
-               VERSION="$(GIT_VERSION)" \
-               WRITERS="$(writers)" \
-               ./man_util.bash $@
+audiod_commands := $(addprefix $(lls_suite_dir)/, \
+       $(addsuffix _cmd.lsg.man, audiod recv filter write))
+filter_commands := $(lls_suite_dir)/filter_cmd.lsg.man
+play_commands := $(lls_suite_dir)/play_cmd.lsg.man
+recv_commands := $(lls_suite_dir)/recv_cmd.lsg.man
+server_commands := $(lls_suite_dir)/server_cmd.lsg.man
+write_commands := $(lls_suite_dir)/write_cmd.lsg.man
+
+$(man_dir)/para_audiod.1: $(audiod_commands)
+$(man_dir)/para_filter.1: $(filter_commands)
+$(man_dir)/para_play.1: $(play_commands)
+$(man_dir)/para_recv.1: $(recv_commands)
+$(man_dir)/para_server.1: $(server_commands)
+$(man_dir)/para_write.1: $(write_commands)
+
+$(man_dir)/para_audiod.1: all_commands := $(audiod_commands)
+$(man_dir)/para_filter.1: all_commands := $(filter_commands)
+$(man_dir)/para_play.1: all_commands := $(play_commands)
+$(man_dir)/para_recv.1: all_commands := $(recv_commands)
+$(man_dir)/para_server.1: all_commands := $(server_commands)
+$(man_dir)/para_write.1: all_commands := $(write_commands)
+
+$(man_dir)/para_%.1: $(lls_suite_dir)/%.lsg.man \
+               $(lls_m4_dir)/copyright.m4 | $(man_dir)
+       $(call SAY, LLSMAN $<)
+       cat $< $(all_commands) > $@
+       $(M4) -D COPYRIGHT_YEAR=$(COPYRIGHT_YEAR) $(lls_m4_dir)/copyright.m4 >> $@
 
 $(object_dir)/%.o: %.c | $(object_dir)
 
-$(object_dir)/opus%.o $(dep_dir)/opus%.d: CPPFLAGS += $(opus_cppflags)
-$(object_dir)/gui.o $(object_dir)/gui%.o $(dep_dir)/gui%.d \
+$(object_dir)/opus%.o: CPPFLAGS += $(opus_cppflags)
+$(object_dir)/gui.o $(object_dir)/gui%.o \
 : CPPFLAGS += $(curses_cppflags)
-$(object_dir)/spx%.o $(dep_dir)/spx%.d: CPPFLAGS += $(speex_cppflags)
-$(object_dir)/flac%.o $(dep_dir)/flac%.d: CPPFLAGS += $(flac_cppflags)
+$(object_dir)/spx%.o: CPPFLAGS += $(speex_cppflags)
+$(object_dir)/flac%.o: CPPFLAGS += $(flac_cppflags)
 
-$(object_dir)/mp3_afh.o $(dep_dir)/mp3_afh.d: CPPFLAGS += $(id3tag_cppflags)
-$(object_dir)/crypt.o $(dep_dir)/crypt.d: CPPFLAGS += $(openssl_cppflags)
-$(object_dir)/gcrypt.o $(dep_dir)/gcrypt.d: CPPFLAGS += $(gcrypt_cppflags)
-$(object_dir)/ao_write.o $(dep_dir)/ao_write.d: CPPFLAGS += $(ao_cppflags)
-$(object_dir)/aac_afh.o $(dep_dir)/aac_afh.d: CPPFLAGS += $(mp4v2_cppflags)
-$(object_dir)/alsa%.o $(dep_dir)/alsa%.d: CPPFLAGS += $(alsa_cppflags)
+$(object_dir)/mp3_afh.o: CPPFLAGS += $(id3tag_cppflags)
+$(object_dir)/openssl.o: CPPFLAGS += $(openssl_cppflags)
+$(object_dir)/gcrypt.o: CPPFLAGS += $(gcrypt_cppflags)
+$(object_dir)/ao_write.o: CPPFLAGS += $(ao_cppflags)
+$(object_dir)/alsa%.o: CPPFLAGS += $(alsa_cppflags)
 
-$(object_dir)/interactive.o $(dep_dir)/interactive.d \
+$(object_dir)/interactive.o \
 : CPPFLAGS += $(readline_cppflags)
 
-$(object_dir)/resample_filter.o $(dep_dir)/resample_filter.d \
+$(object_dir)/resample_filter.o \
 : CPPFLAGS += $(samplerate_cppflags)
 
-$(object_dir)/oss_write.o $(dep_dir)/oss_write.d \
+$(object_dir)/oss_write.o \
 : CPPFLAGS += $(oss_cppflags)
 
-$(object_dir)/ao_write.o $(dep_dir)/ao_write.d \
+$(object_dir)/ao_write.o \
 : CPPFLAGS += $(ao_cppflags) $(pthread_cppflags)
 
-$(object_dir)/mp3dec_filter.o $(dep_dir)/mp3dec_filter.d \
+$(object_dir)/mp3dec_filter.o \
 : CPPFLAGS += $(mad_cppflags)
 
-$(object_dir)/aacdec_filter.o $(dep_dir)/aacdec_filter.d \
-$(object_dir)/aac_common.o $(dep_dir)/aac_common.d \
-$(object_dir)/aac_afh.o $(dep_dir)/aac_afh.d \
+$(object_dir)/aacdec_filter.o \
+$(object_dir)/aac_afh.o \
 : CPPFLAGS += $(faad_cppflags)
 
-$(object_dir)/ogg_afh.o $(dep_dir)/ogg_afh.d \
-$(object_dir)/oggdec_filter.o $(dep_dir)/oggdec_filter.d \
+$(object_dir)/ogg_afh.o \
+$(object_dir)/oggdec_filter.o \
 : CPPFLAGS += $(vorbis_cppflags)
 
-$(object_dir)/spx_common.o $(dep_dir)/spx_common.d \
-$(object_dir)/spxdec_filter.o $(dep_dir)/spxdec_filter.d \
-$(object_dir)/spx_afh.o $(dep_dir)/spx_afh.d \
-$(object_dir)/oggdec_filter.o $(dep_dir)/oggdec_filter.d \
-$(object_dir)/ogg_afh.o $(dep_dir)/ogg_afh.d \
-$(object_dir)/ogg_afh_common.o $(dep_dir)/ogg_afh_common.d \
-$(object_dir)/opus%.o $(dep_dir)/opus%.d \
+$(object_dir)/spx_common.o \
+$(object_dir)/spxdec_filter.o \
+$(object_dir)/spx_afh.o \
+$(object_dir)/oggdec_filter.o \
+$(object_dir)/ogg_afh.o \
+$(object_dir)/ogg_afh_common.o \
+$(object_dir)/opus%.o \
 : CPPFLAGS += $(ogg_cppflags)
 
-$(object_dir)/afs.o $(dep_dir)/afs.d \
-$(object_dir)/aft.o $(dep_dir)/aft.d \
-$(object_dir)/attribute.o $(dep_dir)/attribute.d \
-$(object_dir)/blob.o $(dep_dir)/blob.d  \
-$(object_dir)/mood.o $(dep_dir)/mood.d \
-$(object_dir)/playlist.o $(dep_dir)/playlist.d \
-$(object_dir)/score.o $(dep_dir)/score.d \
-$(object_dir)/server.o $(dep_dir)/server.d \
-$(object_dir)/vss.o $(dep_dir)/vss.d \
-$(object_dir)/command.o $(dep_dir)/command.d \
-$(object_dir)/http_send.o $(dep_dir)/http_send.d \
-$(object_dir)/dccp_send.o $(dep_dir)/dccp_send.d \
-$(object_dir)/udp_send.o $(dep_dir)/udp_send.d \
-$(object_dir)/send_common.o $(dep_dir)/send_common.d \
-$(object_dir)/mm.o $(dep_dir)/mm.d \
+$(object_dir)/afs.o \
+$(object_dir)/aft.o \
+$(object_dir)/attribute.o \
+$(object_dir)/blob.o  \
+$(object_dir)/mood.o \
+$(object_dir)/playlist.o \
+$(object_dir)/score.o \
+$(object_dir)/server.o \
+$(object_dir)/vss.o \
+$(object_dir)/command.o \
+$(object_dir)/http_send.o \
+$(object_dir)/dccp_send.o \
+$(object_dir)/udp_send.o \
+$(object_dir)/send_common.o \
+$(object_dir)/mm.o \
 : CPPFLAGS += $(osl_cppflags)
 
-$(object_dir)/%.cmdline.o: CFLAGS += -Wno-unused-function
 $(object_dir)/compress_filter.o: CFLAGS += -O3
 
-$(object_dir)/%.o: %.c | $(object_dir)
-       @[ -z "$(Q)" ] || echo 'CC $<'
-       $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(STRICT_CFLAGS) $<
-
-$(object_dir)/%.cmdline.o: $(cmdline_dir)/%.cmdline.c $(cmdline_dir)/%.cmdline.h | $(object_dir)
-       @[ -z "$(Q)" ] || echo 'CC $<'
-       $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $<
-
-# The compiler outputs dependencies either as foo.h or as some_directory/foo.h,
-# depending on whether the latter file exists. Since make needs the directory
-# part we prefix the dependency as appropriate.
-$(dep_dir)/%.d: %.c | $(dep_dir)
-       @[ -z "$(Q)" ] || echo 'DEP $<'
-       $(Q) $(CC) $(CPPFLAGS) -MM -MG -MP -MT $@ -MT $(object_dir)/$(*F).o $< \
-               | sed -e "s@ \([a-zA-Z0-9_]\{1,\}\.cmdline.h\)@ $(cmdline_dir)/\1@g" \
-               -e "s@ \([a-zA-Z0-9_]\{1,\}\.command_list.h\)@ $(cmdlist_dir)/\1@g" \
-               -e "s@ \([a-zA-Z0-9_]\{1,\}\.completion.h\)@ $(cmdlist_dir)/\1@g" > $@
+$(object_dir)/%.o: %.c | $(object_dir) $(dep_dir) $(lsg_h) $(yy_h)
+       $(call SAY, CC $<)
+       $(CC) -c -o $@ -MMD -MF $(dep_dir)/$(*F).d -MT $@ $(CPPFLAGS) \
+               $(STRICT_CFLAGS) $(CFLAGS) $<
 
 para_recv para_afh para_play para_server: LDFLAGS += $(id3tag_ldflags)
 para_write para_play para_audiod \
-: LDFLAGS += $(ao_ldflags) $(pthread_ldflags) $(core_audio_ldflags)
+: LDFLAGS += $(ao_ldflags) $(pthread_ldflags)
 para_client para_audioc para_play : LDFLAGS += $(readline_ldflags)
 para_server: LDFLAGS += $(osl_ldflags)
 para_gui: LDFLAGS += $(curses_ldflags)
@@ -301,14 +274,29 @@ para_play \
        $(samplerate_ldflags) \
        -lm
 
+para_mixer: LDFLAGS += -lm
+
 para_write \
 para_play \
 para_audiod \
-para_fade \
+para_mixer \
 : LDFLAGS += \
        $(oss_ldflags) \
        $(alsa_ldflags)
 
+para_afh \
+para_audioc \
+para_audiod \
+para_client \
+para_mixer \
+para_filter \
+para_gui \
+para_play \
+para_recv \
+para_server \
+para_write \
+: LDFLAGS += $(lopsub_ldflags)
+
 para_server \
 para_filter \
 para_audiod \
@@ -323,53 +311,52 @@ para_recv \
        $(faad_ldflags) \
        $(flac_ldflags)
 
-para_server \
-para_play \
-para_afh \
-para_recv \
-: LDFLAGS += \
-       $(mp4v2_ldflags)
-
 para_afh para_recv para_server para_play: LDFLAGS += $(iconv_ldflags)
 
 $(foreach exe,$(executables),$(eval para_$(exe): $$($(exe)_objs)))
 $(prefixed_executables):
-       @[ -z "$(Q)" ] || echo 'LD $@'
-       $(Q) $(CC) $^ -o $@ $(LDFLAGS)
-
-clean:
-       @[ -z "$(Q)" ] || echo 'CLEAN'
-       $(Q) rm -f para_*
-       $(Q) rm -rf $(object_dir)
-
-clean2: clean
-       @[ -z "$(Q)" ] || echo 'CLEAN2'
-       $(Q) rm -rf $(build_dir)
-distclean: clean2 test-clean
-       @[ -z "$(Q)" ] || echo 'DISTCLEAN'
-       $(Q) rm -f Makefile autoscan.log config.status config.log
-       $(Q) rm -f GPATH GRTAGS GSYMS GTAGS
-
+       $(call SAY, LD $@)
+       $(CC) $^ -o $@ $(LDFLAGS)
+
+mostlyclean:
+       $(call SAY, MOSTLYCLEAN)
+       rm -f para_*
+       rm -rf $(object_dir)
+clean: mostlyclean
+       $(call SAY, CLEAN)
+       rm -rf $(build_dir)
+distclean: clean
+       $(call SAY, DISTCLEAN)
+       rm -f Makefile autoscan.log config.status config.log
+       rm -f config.h configure config.h.in
 maintainer-clean: distclean
-       @[ -z "$(Q)" ] || echo 'MAINTAINER-CLEAN'
-       $(Q) rm -f *.tar.bz2 config.h configure config.h.in
-
-install: all man
-       $(MKDIR_P) $(bindir) $(mandir)
-       $(INSTALL) -s --strip-program $(STRIP) -m 755 \
-               $(prefixed_executables) $(bindir)
-       $(INSTALL) -m 644 $(man_pages) $(mandir)
-       $(MKDIR_P) $(vardir) >/dev/null 2>&1 || true # not fatal, so don't complain
-
-$(tarball):
-       $(Q) rm -rf $(tarball) $(tarball_pfx)
-       $(Q) git archive --format=tar --prefix=$(tarball_pfx)/ HEAD \
-               | tar --delete $(tarball_delete) > $(tarball_pfx).tar
-       $(Q) $(MKDIR_P) $(tarball_pfx)
-       $(Q) ./GIT-VERSION-GEN > $(tarball_pfx)/VERSION
-       $(Q) cp $(autocrap) $(tarball_pfx)
-       $(Q) tar rf $(tarball_pfx).tar $(tarball_pfx)/*
-       $(Q) bzip2 -9 $(tarball_pfx).tar
-       $(Q) ls -l $(tarball)
-       $(Q) ln -sf $(tarball) paraslash-git.tar.bz2
-       $(Q) rm -rf $(tarball_pfx)
+       $(call SAY, MAINTAINER-CLEAN)
+       rm -f *.tar.bz2 *.tar.xz
+       rm -f GPATH GRTAGS GSYMS GTAGS
+
+INSTALL ?= install
+INSTALL_PROGRAM ?= $(INSTALL)
+INSTALL_DATA ?= $(INSTALL) -m 644
+ifneq ($(findstring strip, $(MAKECMDGOALS)),)
+       strip_option := -s
+endif
+
+install install-strip: all man
+       $(MKDIR_P) $(DESTDIR)$(bindir) $(DESTDIR)$(mandir)
+       $(INSTALL) $(strip_option) $(prefixed_executables) $(DESTDIR)$(bindir)
+       $(INSTALL_DATA) $(man_pages) $(DESTDIR)$(mandir)
+       $(MKDIR_P) $(DESTDIR)$(vardir) >/dev/null 2>&1 || true # not fatal, so don't complain
+
+$(tarball) dist tarball:
+       $(call SAY, DIST)
+       rm -rf $(tarball) $(tarball_pfx)
+       git archive --format=tar --prefix=$(tarball_pfx)/ HEAD \
+          | tar --delete $(tarball_delete) > $(tarball_pfx).tar
+       $(MKDIR_P) $(tarball_pfx)
+       ./GIT-VERSION-GEN > $(tarball_pfx)/VERSION
+       cp $(autocrap) $(tarball_pfx)
+       tar rf $(tarball_pfx).tar $(tarball_pfx)/*
+       xz -9 $(tarball_pfx).tar
+       ls -l $(tarball)
+       ln -sf $(tarball) paraslash-git.tar.xz
+       rm -rf $(tarball_pfx)
diff --git a/NEWS.md b/NEWS.md
index d6b0abc..7dc331f 100644 (file)
--- a/NEWS.md
+++ b/NEWS.md
@@ -18,9 +18,118 @@ Downloads:
 [tarball](./releases/paraslash-0.5.9.tar.bz2),
 [signature](./releases/paraslash-0.5.9.tar.bz2.asc)
 
+----------------------------------------
+0.6.3 (2021-02-18) "generalized activity"
+-----------------------------------------
+
+It has been a while since the last release, but paraslash-0.6.3
+finally has arrived. It contains a fair number of improvements,
+features and fixes all over the place.
+
+- The ff command now accepts a negative argument to instruct the
+  virtual streaming system to jump backwards in the current audio
+  stream. The old syntax (e.g., "ff 30-") is still supported but it
+  is deprecated and no longer documented. The compatibility code is
+  sheduled for removal after 0.7.0.
+- para_afh: New option: --preserve to reset the modification time to
+  the value of the original file after meta data modification.
+- Overhaul of the compress filter code. The refined algorithm should
+  reduce clipping. The meaning of --aggressiveness has changed, see the
+  updated and extended documentation of the compress filter for details.
+- Cleanup of the audio format handler code.
+- We now build the tree using the .ONESHELL feature of GNU make,
+  which results in a significant speedup.
+- Two robustness fixes for FreeBSD.
+- para_client now supports RFC4716 private keys as generated with
+  ssh-keygen -m RFC4716. In fact, this key format has been made the
+  default, and the former PEM keys will be depreciated at some point.
+- The ogg audio format handlers learned to detect holes and now report
+  the correct duration also if ogg pages are missing in the file. This
+  affects ogg/vorbis ogg/speex and ogg/opus.
+- Robustness improvements for para_mixer.
+- A fix for an old bug that could cause the server to crash or report
+  garbage in its status output.
+- New para_play option: --end-of-playlist
+- Streaming m4a files over udp has been improved.
+
+Downloads:
+[tarball](./releases/paraslash-0.6.3.tar.xz),
+[signature](./releases/paraslash-0.6.3.tar.xz.asc)
+
+--------------------------------------
+0.6.2 (2018-06-30) "elastic diversity"
+--------------------------------------
+
+The main new feature of this release is the --listen-address option
+for para_server and para_audiod. But there are also a large number of
+small improvements and bug fixes all over the place. The user-visible
+changes include
+
+- para_gui no longer waits up to one second to update the screen when
+  the geometry of the terminal changes.
+- Minor documentation improvements.
+- Improvements to the crypto subsystem.
+- The server subcommand "task" has been deprecated. It still works,
+  but prints nothing. It will be removed in the next major release.
+- Server log output is now serialized, avoiding issues with partial
+  lines.
+- It is now possible to switch to a different afs database by changing
+  the server configuration and sending SIGHUP to the server process.
+- New server options: --listen--address, --http-listen-address and
+  --dccp-listen-address. These options restrict the set of listening
+  addresses of the TCP and DCCP sockets of the server process.
+- The help commands of server, audiod, play now support --long. By default,
+  the short help is shown.
+- The code which merges the command line options with the config file
+  options has been consolidated.
+- If the current audio file is renamed, the status items are now updated
+  accordingly.
+- After the server process received SIGHUP, changes to the current audio
+  file did not trigger an update of the status items. This has been fixed.
+
+Downloads:
+[tarball](./releases/paraslash-0.6.2.tar.xz),
+[signature](./releases/paraslash-0.6.2.tar.xz.asc)
+
+----------------------------------------
+0.6.1 (2017-09-23) "segmented iteration"
+----------------------------------------
+
+The highlight of this release is the version 2 mood parser. But there
+is a lot more than that, as summarized in the list below. And of
+course we have many small usability improvements and bug fixes not
+mentioned here.
+
+- A more intuitive syntax for moods ("version 2 moods"). The
+  traditional version 1 moods are still supported but are deprecated
+  now. Removal of the version 1 mood parser is scheduled for the next
+  major release.
+- Flex and bison are now required to build para_server.
+- New sort order for the ls command: -s=h sorts the ls output by hash
+  value of the audio file.
+- autogen.sh now runs the test suite after a successful build.
+- The contents of overview.pdf have been integrated into the user
+  manual.
+- Fixed sized audio format headers for ogg/opus streams.
+- The doxygen source browser has been disabled temporarily. The
+  API reference is still online, though.
+- Overhaul of the source code documentation.
+- The deprecated --full-path option of the ls command has been
+  removed. It was a no-op since 0.6.0.
+- The wma decoder has been cleaned up and its bitstream API made
+  more robust.
+- The image/lyrics ID status items of the current audio file are now
+  updated on changes. This affects para_gui, which used to report the
+  old value until EOF.
+
+Downloads:
+[tarball](./releases/paraslash-0.6.1.tar.xz),
+[signature](./releases/paraslash-0.6.1.tar.xz.asc)
+
 ---------------------------------------
 0.5.8 (2017-09-23) "branching parabola"
 ---------------------------------------
+
 A couple of fixes for serious old bugs have accummulated in the maint
 branch since paraslash-0.5.7 was released nine months ago. So here
 is another 0.5.x release. As usual, all commits in the maint branch
@@ -31,6 +140,78 @@ Downloads:
 [tarball](./releases/paraslash-0.5.8.tar.bz2),
 [signature](./releases/paraslash-0.5.8.tar.bz2.asc)
 
+-------------------------------
+0.6.0 (2017-04-28) "fuzzy flux"
+-------------------------------
+
+The highlights of this release are the conversion of all option parsers
+to the lopsub library and the improved AAC support. But there are
+many other user-visible changes all over the place. Also a lot of
+old cruft has been removed, leading to incompatible changes. Hence
+the new major version number.
+
+- Support for Mac OS X has been removed.
+- On Linux systems, glibc-2.17 or newer is required to build the
+  source tree.
+- Support for RSA public keys in ASN format (as generated by openssl
+  genrsa) has been removed. These keys have been deprecated since
+  2011, so users should have long switched to keys generated with
+  ssh-keygen(1).
+- If libgcrypt is used as the crypto library, we now require version
+  1.5.0 (released in 2011) or later.
+- The insecure RC4 stream cipher has been removed. It was superseded
+  by aes_ctr128 three years ago but the RC4 code had been kept for
+  backwards compatibility.
+- On Linux, abstract unix domain sockets are used unconditionally.
+- The "install" target no longer strips executables, the new
+  install-strip target can be used to get the old behaviour.
+- The clean targets have been renamed: clean2 is gone, and the new
+  mostlyclean removes only the executables and object files.
+- New target: check (identical to test).
+- The DESTDIR make variable is honored to prepend a path to the
+  installation directory. This feature is orthogonal to the --prefix
+  option to configure.
+- Minor WMA cleanups.
+- The aac audio format handler has been rewritten to use the mp4ff library.
+  See the manual for how to install the library on your system.
+- New status item: max_chunk_size. The value is stored in a previously
+  unused field of the afhi object of the aft table. Although backwards
+  compatible, users are encouraged to re-add m4a files to populate
+  the new field.
+- No more chunk tables for aac. Chunk boundaries are determined
+  dynamically at stream time.
+- Release and master branch tarballs are now compressed with xz rather
+  than bzip2.
+- The lopsub package is required to install the paraslash package.
+  Gengetopt is no longer needed.
+- make dep is gone. Dependencies have been created automatically for
+  a long time and it was never necessary to run make dep manually.
+- para_gui lost its --timeout option.
+- Most manual pages have been extended to include an overall
+  description of the command.
+- The --stream-delay option of para_audiod has been removed. It had
+  been a no-op for many years.
+- The deprecated --path option of the server ls command has been
+  removed.  The command now prints full paths by default, making
+  --full-path a no-op. Hence --full-path has been depreacted and is
+  scheduled for removal in v0.6.1.
+- It is now possible to use 'CFLAGS' to override the default compiler
+  options.
+- para_fade has been renamed to para_mixer. The four modes of operation
+  (set, fade, snooze, sleep) are implemented as subcommands of the
+  new program.
+- The sleep subcommand of para_mixer (former sleep mode of para_fade)
+  lost its --wake-hour and --wake-min options in favor of the new
+  --wake-time option which also accepts relative wakeup times like
+  "+9:30".
+- The new --fade-exponent option of para_mixer allows for non-linear
+  channel fading.
+- The new logo.
+
+Downloads:
+[tarball](./releases/paraslash-0.6.0.tar.xz),
+[signature](./releases/paraslash-0.6.0.tar.xz.asc)
+
 -------------------------------------
 0.5.7 (2016-12-31) "semantic density"
 -------------------------------------
diff --git a/aac.h b/aac.h
deleted file mode 100644 (file)
index eeed252..0000000
--- a/aac.h
+++ /dev/null
@@ -1,14 +0,0 @@
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
-
-/** \file aac.h Exported symbols from aac_common.c. */
-
-#include <neaacdec.h>
-
-NeAACDecHandle aac_open(void);
-int aac_find_esds(char *buf, size_t buflen, size_t *skip,
-               unsigned long *decoder_length);
-ssize_t aac_find_entry_point(char *buf, size_t buflen, size_t *skip);
index 1c7fd70..2b3dd2c 100644 (file)
--- a/aac_afh.c
+++ b/aac_afh.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 /*
  * based in parts on libfaad, Copyright (C) 2003-2005 M. Bakker,
  * Ahead Software AG
 /** \file aac_afh.c para_server's aac audio format handler. */
 
 #include <regex.h>
-#include <mp4v2/mp4v2.h>
+#include <neaacdec.h>
 
 #include "para.h"
+
+/* To get the mp4ff_tag_t and mp4ff_metadata_t typedefs. */
+#define USE_TAGGING
+#include <mp4ff.h>
+
 #include "error.h"
 #include "portable_io.h"
 #include "afh.h"
 #include "string.h"
-#include "aac.h"
 #include "fd.h"
 
-static int aac_find_stsz(char *buf, size_t buflen, off_t *skip)
+
+struct aac_afh_context {
+       const void *map;
+       size_t mapsize;
+       size_t fpos;
+       int32_t track;
+       mp4ff_t *mp4ff;
+       mp4AudioSpecificConfig masc;
+       mp4ff_callback_t cb;
+};
+
+static uint32_t aac_afh_read_cb(void *user_data, void *dest, uint32_t want)
 {
-       int i;
-
-       for (i = 0; i + 16 < buflen; i++) {
-               char *p = buf + i;
-               unsigned sample_count, sample_size;
-
-               if (p[0] != 's' || p[1] != 't' || p[2] != 's' || p[3] != 'z')
-                       continue;
-               PARA_DEBUG_LOG("found stsz@%d\n", i);
-               i += 8;
-               sample_size = read_u32_be(buf + i);
-               PARA_DEBUG_LOG("sample size: %u\n", sample_size);
-               i += 4;
-               sample_count = read_u32_be(buf + i);
-               i += 4;
-               PARA_DEBUG_LOG("sample count: %u\n", sample_count);
-               *skip = i;
-               return sample_count;
+       struct aac_afh_context *c = user_data;
+       uint32_t have, rv;
+
+       if (want == 0 || c->fpos >= c->mapsize) {
+               PARA_INFO_LOG("failed attempt to read %u bytes @%zu\n", want,
+                       c->fpos);
+               errno = EAGAIN;
+               return -1;
        }
-       return -E_STSZ;
+       have = c->mapsize - c->fpos;
+       rv = PARA_MIN(have, want);
+       PARA_DEBUG_LOG("reading %u bytes @%zu\n", rv, c->fpos);
+       memcpy(dest, c->map + c->fpos, rv);
+       c->fpos += rv;
+       return rv;
 }
 
-static int atom_cmp(const char *buf1, const char *buf2)
+static uint32_t aac_afh_seek_cb(void *user_data, uint64_t pos)
 {
-       return memcmp(buf1, buf2, 4)? 1 : 0;
+       struct aac_afh_context *c = user_data;
+       c->fpos = pos;
+       return 0;
 }
 
-static int read_atom_header(char *buf, uint64_t *subsize, char type[5])
+static int32_t aac_afh_get_track(mp4ff_t *mp4ff, mp4AudioSpecificConfig *masc)
 {
-       uint64_t size = read_u32_be(buf);
-
-       memcpy(type, buf + 4, 4);
-       type[4] = '\0';
-
-       PARA_DEBUG_LOG("size: %llu, type: %s\n", (long long unsigned)size, type);
-       if (size != 1) {
-               *subsize = size;
-               return 8;
+       int32_t i, rc, num_tracks = mp4ff_total_tracks(mp4ff);
+
+       assert(num_tracks >= 0);
+       for (i = 0; i < num_tracks; i++) {
+               unsigned char *buf = NULL;
+               unsigned buf_size = 0;
+
+               mp4ff_get_decoder_config(mp4ff, i, &buf, &buf_size);
+               if (buf) {
+                       rc = NeAACDecAudioSpecificConfig(buf, buf_size, masc);
+                       free(buf);
+                       if (rc < 0)
+                               continue;
+                       return i;
+               }
        }
-       buf += 4;
-       size = 0;
-       size = read_u64_be(buf);
-       *subsize = size;
-       return 16;
+       return -1; /* no audio track */
 }
 
-static char *get_tag(char *p, int size)
+static int aac_afh_open(const void *map, size_t mapsize, void **afh_context)
 {
-       char *buf;
-
-       assert(size > 0);
-       buf = para_malloc(size + 1);
-
-       memcpy(buf, p, size);
-       buf[size] = '\0';
-       PARA_DEBUG_LOG("size: %d: %s\n", size, buf);
-       return buf;
+       int ret;
+       struct aac_afh_context *c = para_malloc(sizeof(*c));
+
+       c->map = map;
+       c->mapsize = mapsize;
+       c->fpos = 0;
+       c->cb.read = aac_afh_read_cb;
+       c->cb.seek = aac_afh_seek_cb;
+       c->cb.user_data = c;
+
+       ret = -E_MP4FF_OPEN;
+       c->mp4ff = mp4ff_open_read(&c->cb);
+       if (!c->mp4ff)
+               goto free_ctx;
+       c->track = aac_afh_get_track(c->mp4ff, &c->masc);
+       ret = -E_MP4FF_TRACK;
+       if (c->track < 0)
+               goto close_mp4ff;
+       *afh_context = c;
+       return 0;
+close_mp4ff:
+       mp4ff_close(c->mp4ff);
+free_ctx:
+       free(c);
+       *afh_context = NULL;
+       return ret;
 }
 
-static void read_tags(char *buf, size_t buflen, struct afh_info *afhi)
+static void aac_afh_close(void *afh_context)
 {
-       char *p = buf;
+       struct aac_afh_context *c = afh_context;
+       mp4ff_close(c->mp4ff);
+       free(c);
+}
 
-       while (p + 32 < buf + buflen) {
-               char *q, type1[5], type2[5];
-               uint64_t size1, size2;
-               int ret, ret2;
+/**
+ * Libmp4ff function to reposition the file to the given sample.
+ *
+ * \param f The opaque handle returned by mp4ff_open_read().
+ * \param track The number of the (audio) track.
+ * \param sample Destination.
+ *
+ * We need this function to obtain the offset of the sample within the audio
+ * file. Unfortunately, it is not exposed in the mp4ff header.
+ *
+ * \return This function always returns 0.
+ */
+int32_t mp4ff_set_sample_position(mp4ff_t *f, const int32_t track, const int32_t sample);
 
-               ret = read_atom_header(p, &size1, type1);
-               ret2 = read_atom_header(p + ret, &size2, type2);
+static int aac_afh_get_chunk(long unsigned chunk_num, void *afh_context,
+               const char **buf, size_t *len)
+{
+       struct aac_afh_context *c = afh_context;
+       int32_t ss;
+       size_t offset;
+
+       assert(chunk_num <= INT_MAX);
+       /* this function always returns zero */
+       mp4ff_set_sample_position(c->mp4ff, c->track, chunk_num);
+       offset = c->fpos;
+       ss = mp4ff_read_sample_getsize(c->mp4ff, c->track, chunk_num);
+       if (ss <= 0)
+               return -E_MP4FF_BAD_SAMPLE;
+       assert(ss + offset <= c->mapsize);
+       *buf = c->map + offset;
+       *len = ss;
+       return 1;
+}
 
-               if (size2 <= 16 || atom_cmp(type2, "data")) {
-                       p += size1;
-                       continue;
-               }
-               size2 -= 16;
-               q = p + ret + ret2 + 8;
-               if (q + size2 > buf + buflen)
-                       break;
-               if (!atom_cmp(type1, "\xa9" "ART"))
-                       afhi->tags.artist = get_tag(q, size2);
-               else if (!atom_cmp(type1, "\xa9" "alb"))
-                       afhi->tags.album = get_tag(q, size2);
-               else if (!atom_cmp(type1, "\xa9" "nam"))
-                       afhi->tags.title = get_tag(q, size2);
-               else if (!atom_cmp(type1, "\xa9" "cmt"))
-                       afhi->tags.comment = get_tag(q, size2);
-               else if (!atom_cmp(type1, "\xa9" "day"))
-                       afhi->tags.year = get_tag(q, size2);
-               p += size1;
-       }
+static void _aac_afh_get_taginfo(const mp4ff_t *mp4ff, struct taginfo *tags)
+{
+       mp4ff_meta_get_artist(mp4ff, &tags->artist);
+       mp4ff_meta_get_title(mp4ff, &tags->title);
+       mp4ff_meta_get_date(mp4ff, &tags->year);
+       mp4ff_meta_get_album(mp4ff, &tags->album);
+       mp4ff_meta_get_comment(mp4ff, &tags->comment);
 }
 
-static void read_meta(char *buf, size_t buflen, struct afh_info *afhi)
+/*
+ * Init m4a file and write some tech data to given pointers.
+ */
+static int aac_get_file_info(char *map, size_t numbytes, __a_unused int fd,
+               struct afh_info *afhi)
 {
-       char *p = buf;
+       int ret;
+       int32_t rv;
+       struct aac_afh_context *c;
+       int64_t tmp;
+       const char *buf;
+       size_t sz;
+       uint32_t n;
+
+       ret = aac_afh_open(map, numbytes, (void **)&c);
+       if (ret < 0)
+               return ret;
 
-       while (p + 4 < buf + buflen) {
+       ret = -E_MP4FF_BAD_SAMPLERATE;
+       rv = mp4ff_get_sample_rate(c->mp4ff, c->track);
+       if (rv <= 0)
+               goto close;
+       afhi->frequency = rv;
 
-               if (p[0] != 'i' || p[1] != 'l' || p[2] != 's' || p[3] != 't') {
-                       p++;
-                       continue;
-               }
-               p += 4;
-               return read_tags(p, buflen - (p - buf), afhi);
+       ret = -E_MP4FF_BAD_CHANNEL_COUNT;
+       rv = mp4ff_get_channel_count(c->mp4ff, c->track);
+       if (rv <= 0)
+               goto close;
+       afhi->channels = rv;
+
+       ret = -E_MP4FF_BAD_SAMPLE_COUNT;
+       rv = mp4ff_num_samples(c->mp4ff, c->track);
+       if (rv <= 0)
+               goto close;
+       afhi->chunks_total = rv;
+       afhi->max_chunk_size = 0;
+       for (n = 0; n < afhi->chunks_total; n++) {
+               if (aac_afh_get_chunk(n, c, &buf, &sz) < 0)
+                       break;
+               afhi->max_chunk_size = PARA_MAX((size_t)afhi->max_chunk_size, sz);
        }
+
+       tmp = c->masc.sbr_present_flag == 1? 2048 : 1024;
+       afhi->seconds_total = tmp * afhi->chunks_total / afhi->frequency;
+       ms2tv(1000 * tmp / afhi->frequency, &afhi->chunk_tv);
+
+       if (aac_afh_get_chunk(0, c, &buf, &sz) >= 0)
+               numbytes -= buf - map;
+       afhi->bitrate = 8 * numbytes / afhi->seconds_total / 1000;
+       _aac_afh_get_taginfo(c->mp4ff, &afhi->tags);
+       ret = 1;
+close:
+       aac_afh_close(c);
+       return ret;
 }
 
-static void aac_get_taginfo(char *buf, size_t buflen, struct afh_info *afhi)
+static uint32_t aac_afh_meta_read_cb(void *user_data, void *dest, uint32_t want)
 {
-       int i;
-       uint64_t subsize;
-       char type[5];
-
-       for (i = 0; i + 24 < buflen; i++) {
-               char *p = buf + i;
-               if (p[0] != 'm' || p[1] != 'e' || p[2] != 't' || p[3] != 'a')
-                       continue;
-               PARA_INFO_LOG("found metadata at offset %d\n", i);
-               i += 8;
-               p = buf + i;
-               i += read_atom_header(p, &subsize, type);
-               p = buf + i;
-               return read_meta(p, buflen - i, afhi);
-       }
-       PARA_INFO_LOG("no meta data\n");
+       int fd = *(int *)user_data;
+       return read(fd, dest, want);
 }
 
-static ssize_t aac_compute_chunk_table(struct afh_info *afhi,
-               char *map, size_t numbytes)
+static uint32_t aac_afh_meta_seek_cb(void *user_data, uint64_t pos)
 {
-       int ret, i;
-       size_t sum = 0;
-       off_t skip;
+       int fd = *(int *)user_data;
+       return lseek(fd, pos, SEEK_SET);
+}
 
-       ret = aac_find_stsz(map, numbytes, &skip);
-       if (ret < 0)
-               return ret;
-       afhi->chunks_total = ret;
-       PARA_DEBUG_LOG("sz table has %" PRIu32 " entries\n", afhi->chunks_total);
-       afhi->chunk_table = para_malloc((afhi->chunks_total + 1) * sizeof(size_t));
-       for (i = 1; i <= afhi->chunks_total; i++) {
-               if (skip + 4 > numbytes)
-                       break;
-               sum += read_u32_be(map + skip);
-               afhi->chunk_table[i] = sum;
-               skip += 4;
-//             if (i < 10 || i + 10 > afhi->chunks_total)
-//                     PARA_DEBUG_LOG("offset #%d: %zu\n", i, afhi->chunk_table[i]);
-       }
-       return skip;
+static uint32_t aac_afh_meta_write_cb(void *user_data, void *dest, uint32_t want)
+{
+       int fd = *(int *)user_data;
+       return write(fd, dest, want);
 }
 
-static int aac_set_chunk_tv(struct afh_info *afhi,
-               mp4AudioSpecificConfig *mp4ASC, uint32_t *seconds)
+static uint32_t aac_afh_meta_truncate_cb(void *user_data)
 {
-       float tmp = mp4ASC->sbr_present_flag == 1? 2047 : 1023;
-       struct timeval total;
-       long unsigned ms;
-
-       if (!mp4ASC->samplingFrequency)
-               return -E_MP4ASC;
-       ms = 1000.0 * afhi->chunks_total * tmp / mp4ASC->samplingFrequency;
-       ms2tv(ms, &total);
-       tv_divide(afhi->chunks_total, &total, &afhi->chunk_tv);
-       PARA_INFO_LOG("%luHz, %lus (%" PRIu32 " x %lums)\n",
-               mp4ASC->samplingFrequency, ms / 1000,
-               afhi->chunks_total, tv2ms(&afhi->chunk_tv));
-       if (ms < 1000)
-               return -E_MP4ASC;
-       *seconds = ms / 1000;
-       return 1;
+       int fd = *(int *)user_data;
+       off_t offset = lseek(fd, 0, SEEK_CUR);
+       return ftruncate(fd, offset);
 }
 
-/*
- * Init m4a file and write some tech data to given pointers.
- */
-static int aac_get_file_info(char *map, size_t numbytes, __a_unused int fd,
-               struct afh_info *afhi)
+static void replace_tag(mp4ff_tag_t *tag, const char *new_val, bool *found)
 {
-       int i;
-       size_t skip;
-       ssize_t ret;
-       unsigned long rate = 0, decoder_len;
-       unsigned char channels = 0;
-       mp4AudioSpecificConfig mp4ASC;
-       NeAACDecHandle handle = NULL;
-
-       ret = aac_find_esds(map, numbytes, &skip, &decoder_len);
-       if (ret < 0)
-               goto out;
-       aac_get_taginfo(map, numbytes, afhi);
-       handle = aac_open();
-       ret = -E_AAC_AFH_INIT;
-       if (NeAACDecInit(handle, (unsigned char *)map + skip, decoder_len,
-                       &rate, &channels))
-               goto out;
-       if (!channels)
-               goto out;
-       PARA_DEBUG_LOG("rate: %lu, channels: %d\n", rate, channels);
-       ret = -E_MP4ASC;
-       if (NeAACDecAudioSpecificConfig((unsigned char *)map + skip,
-                       numbytes - skip, &mp4ASC))
-               goto out;
-       if (!mp4ASC.samplingFrequency)
-               goto out;
-       ret = aac_compute_chunk_table(afhi, map, numbytes);
-       if (ret < 0)
-               goto out;
-       skip = ret;
-       ret = aac_set_chunk_tv(afhi, &mp4ASC, &afhi->seconds_total);
-       if (ret < 0)
-               goto out;
-       ret = aac_find_entry_point(map + skip, numbytes - skip, &skip);
-       if (ret < 0)
-               goto out;
-       afhi->chunk_table[0] = ret;
-       for (i = 1; i<= afhi->chunks_total; i++)
-               afhi->chunk_table[i] += ret;
-       afhi->channels = channels;
-       afhi->frequency = rate;
-       ret = (afhi->chunk_table[afhi->chunks_total] - afhi->chunk_table[0]) * 8; /* bits */
-       ret += (channels * afhi->seconds_total * 500); /* avoid rounding error */
-       afhi->bitrate = ret / (channels * afhi->seconds_total * 1000);
-       ret = 1;
-out:
-       if (handle)
-               NeAACDecClose(handle);
-       return ret;
+       free(tag->value);
+       tag->value = para_strdup(new_val);
+       *found = true;
 }
 
-static int aac_rewrite_tags(const char *map, size_t mapsize,
-               struct taginfo *tags, int fd, const char *filename)
+static void add_tag(mp4ff_metadata_t *md, const char *item, const char *value)
 {
-       MP4FileHandle h;
-       const MP4Tags *mdata;
-       int ret = write_all(fd, map, mapsize);
+       md->tags[md->count].item = para_strdup(item);
+       md->tags[md->count].value = para_strdup(value);
+       md->count++;
+}
 
+static int aac_afh_rewrite_tags(const char *map, size_t mapsize,
+               struct taginfo *tags, int fd, __a_unused const char *filename)
+{
+       int ret, i;
+       int32_t rv;
+       mp4ff_metadata_t metadata;
+       mp4ff_t *mp4ff;
+       mp4ff_callback_t cb = {
+               .read = aac_afh_meta_read_cb,
+               .seek = aac_afh_meta_seek_cb,
+               .write = aac_afh_meta_write_cb,
+               .truncate = aac_afh_meta_truncate_cb,
+               .user_data = &fd
+       };
+       bool found_artist = false, found_title = false, found_album = false,
+               found_year = false, found_comment = false;
+
+       ret = write_all(fd, map, mapsize);
        if (ret < 0)
                return ret;
        lseek(fd, 0, SEEK_SET);
-       h = MP4Modify(filename, 0);
-       if (!h) {
-               PARA_ERROR_LOG("MP4Modify() failed, fd = %d\n", fd);
-               return -E_MP4V2;
-       }
-       mdata = MP4TagsAlloc();
-       assert(mdata);
-       if (!MP4TagsFetch(mdata, h)) {
-               PARA_ERROR_LOG("MP4Tags_Fetch() failed\n");
-               ret = -E_MP4V2;
-               goto close;
-       }
 
-       if (!MP4TagsSetAlbum(mdata, tags->album)) {
-               PARA_ERROR_LOG("Could not set album\n");
-               ret = -E_MP4V2;
-               goto tags_free;
-       }
-       if (!MP4TagsSetArtist(mdata, tags->artist)) {
-               PARA_ERROR_LOG("Could not set album\n");
-               ret = -E_MP4V2;
-               goto tags_free;
-       }
-       if (!MP4TagsSetComments(mdata, tags->comment)) {
-               PARA_ERROR_LOG("Could not set comment\n");
-               ret = -E_MP4V2;
-               goto tags_free;
-       }
-       if (!MP4TagsSetName(mdata, tags->title)) {
-               PARA_ERROR_LOG("Could not set title\n");
-               ret = -E_MP4V2;
-               goto tags_free;
-       }
-       if (!MP4TagsSetReleaseDate(mdata, tags->year)) {
-               PARA_ERROR_LOG("Could not set release date\n");
-               ret = -E_MP4V2;
-               goto tags_free;
-       }
+       mp4ff = mp4ff_open_read_metaonly(&cb);
+       if (!mp4ff)
+               return -E_MP4FF_OPEN;
 
-       if (!MP4TagsStore(mdata, h)) {
-               PARA_ERROR_LOG("Could not store tags\n");
-               ret = -E_MP4V2;
-               goto tags_free;
+       ret = -E_MP4FF_META_READ;
+       rv = mp4ff_meta_get_num_items(mp4ff);
+       if (rv < 0)
+               goto close;
+       metadata.count = rv;
+       PARA_NOTICE_LOG("%d metadata item(s) found\n", rv);
+
+       metadata.tags = para_malloc((metadata.count + 5) * sizeof(mp4ff_tag_t));
+       for (i = 0; i < metadata.count; i++) {
+               mp4ff_tag_t *tag = metadata.tags + i;
+
+               ret = -E_MP4FF_META_READ;
+               if (!mp4ff_meta_get_by_index(mp4ff, i, &tag->item, &tag->value))
+                       goto free_tags;
+               PARA_INFO_LOG("found: %s: %s\n", tag->item, tag->value);
+               if (!strcmp(tag->item, "artist"))
+                       replace_tag(tag, tags->artist, &found_artist);
+               else if (!strcmp(tag->item, "title"))
+                       replace_tag(tag, tags->title, &found_title);
+               else if (!strcmp(tag->item, "album"))
+                       replace_tag(tag, tags->album, &found_album);
+               else if (!strcmp(tag->item, "date"))
+                       replace_tag(tag, tags->year, &found_year);
+               else if (!strcmp(tag->item, "comment"))
+                       replace_tag(tag, tags->comment, &found_comment);
        }
+       if (!found_artist)
+               add_tag(&metadata, "artist", tags->artist);
+       if (!found_title)
+               add_tag(&metadata, "title", tags->title);
+       if (!found_album)
+               add_tag(&metadata, "album", tags->album);
+       if (!found_year)
+               add_tag(&metadata, "date", tags->year);
+       if (!found_comment)
+               add_tag(&metadata, "comment", tags->comment);
+       ret = -E_MP4FF_META_WRITE;
+       if (!mp4ff_meta_update(&cb, &metadata))
+               goto free_tags;
        ret = 1;
-tags_free:
-       MP4TagsFree(mdata);
+free_tags:
+       for (; i > 0; i--) {
+               free(metadata.tags[i - 1].item);
+               free(metadata.tags[i - 1].value);
+       }
+       free(metadata.tags);
 close:
-       MP4Close(h, 0);
+       mp4ff_close(mp4ff);
        return ret;
 }
 
 static const char * const aac_suffixes[] = {"m4a", "mp4", NULL};
+
 /**
- * the init function of the aac audio format handler
+ * The audio format handler for the Advanced Audio Codec.
  *
- * \param afh pointer to the struct to initialize
+ * This is only compiled in if the faad library is installed.
  */
-void aac_afh_init(struct audio_format_handler *afh)
-{
-       afh->get_file_info = aac_get_file_info,
-       afh->suffixes = aac_suffixes;
-       afh->rewrite_tags = aac_rewrite_tags;
-}
+const struct audio_format_handler aac_afh = {
+       .get_file_info = aac_get_file_info,
+       .suffixes = aac_suffixes,
+       .rewrite_tags = aac_afh_rewrite_tags,
+       .open = aac_afh_open,
+       .get_chunk = aac_afh_get_chunk,
+       .close = aac_afh_close,
+};
diff --git a/aac_common.c b/aac_common.c
deleted file mode 100644 (file)
index 812c742..0000000
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
-/*
- * based in parts on libfaad, Copyright (C) 2003-2005 M. Bakker,
- * Ahead Software AG
- */
-
-/** \file aac_common.c Common functions of aac_afh and aadcec. */
-
-#include "para.h"
-#include "aac.h"
-#include "error.h"
-#include "portable_io.h"
-
-/**
- * Get a new libfaad decoder handle.
- *
- * \return The handle returned by NeAACDecOpen().
- */
-NeAACDecHandle aac_open(void)
-{
-       NeAACDecHandle h = NeAACDecOpen();
-       NeAACDecConfigurationPtr c = NeAACDecGetCurrentConfiguration(h);
-
-       c->defObjectType = LC;
-       c->outputFormat = FAAD_FMT_16BIT;
-       c->downMatrix = 0;
-       NeAACDecSetConfiguration(h, c);
-       return h;
-}
-
-static unsigned long aac_read_decoder_length(char *buf, int *description_len)
-{
-       uint8_t b;
-       uint8_t numBytes = 0;
-       unsigned long length = 0;
-
-       do {
-               b = buf[numBytes];
-               numBytes++;
-               length = (length << 7) | (b & 0x7F);
-       } while
-               ((b & 0x80) && numBytes < 4);
-       *description_len = numBytes;
-       return length;
-}
-
-/**
- * search for the position and the length of the decoder configuration
- *
- * \param buf buffer to seach
- * \param buflen length of \a buf
- * \param skip Upon succesful return, this contains the offset in \a buf where
- * the decoder config starts.
- * \param decoder_length result pointer that is filled in with the length of
- * the decoder configuration on success.
- *
- * \return positive on success, negative on errors
- */
-int aac_find_esds(char *buf, size_t buflen, size_t *skip,
-               unsigned long *decoder_length)
-{
-       size_t i;
-
-       for (i = 0; i + 4 < buflen; i++) {
-               char *p = buf + i;
-               int description_len;
-
-               if (p[0] != 'e' || p[1] != 's' || p[2] != 'd' || p[3] != 's')
-                       continue;
-               i += 8;
-               p = buf + i;
-               PARA_INFO_LOG("found esds@%zu, next: %x\n", i, (unsigned)*p);
-               if (*p == 3)
-                       i += 8;
-               else
-                       i += 6;
-               p = buf + i;
-               PARA_INFO_LOG("next: %x\n", (unsigned)*p);
-               if (*p != 4)
-                       continue;
-               i += 18;
-               p = buf + i;
-               PARA_INFO_LOG("next: %x\n", (unsigned)*p);
-               if (*p != 5)
-                       continue;
-               i++;
-               p = buf + i;
-               *decoder_length = aac_read_decoder_length(p, &description_len);
-               PARA_INFO_LOG("decoder length: %lu\n", *decoder_length);
-               i += description_len;
-               *skip = i;
-               return 1;
-       }
-       return -E_ESDS;
-}
-
-/**
- * search for the first entry in the stco table
- *
- * \param buf buffer to seach
- * \param buflen length of \a buf
- * \param skip Upon succesful return, this contains the number
- * of bytes to skip from the input buffer.
- *
- * \return the position of the first entry in the table on success,
- * -E_STCO on errors.
- */
-ssize_t aac_find_entry_point(char *buf, size_t buflen, size_t *skip)
-{
-       ssize_t ret;
-       size_t i;
-
-       for (i = 0; i + 20 < buflen; i++) {
-               char *p = buf + i;
-
-               if (p[0] != 's' || p[1] != 't' || p[2] != 'c' || p[3] != 'o')
-                       continue;
-               PARA_INFO_LOG("found stco@%zu\n", i);
-               i += 12;
-               ret = read_u32_be(buf + i); /* first offset */
-               i += 4;
-               PARA_INFO_LOG("entry point: %zd\n", ret);
-               *skip = i;
-               return ret;
-       }
-       PARA_WARNING_LOG("stco not found, buflen: %zu\n", buflen);
-       return -E_STCO;
-}
index 5725ce0..a2459d8 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 /*
  * based in parts on libfaad, Copyright (C) 2003-2005 M. Bakker,
  * Ahead Software AG
 /** \file aacdec_filter.c paraslash's aac (m4a) decoder. */
 
 #include <regex.h>
+#include <neaacdec.h>
 
 #include "para.h"
+#include "portable_io.h"
 #include "list.h"
 #include "sched.h"
-#include "ggo.h"
 #include "buffer_tree.h"
 #include "filter.h"
 #include "error.h"
 #include "string.h"
-#include "aac.h"
 
 /** Give up decoding after that many errors. */
 #define MAX_ERRORS 20
 /**
  * data specific to the aacdec filter
  *
- * \sa filter, filter_node
+ * \sa \ref filter, \ref filter_node.
  */
 struct private_aacdec_data {
        /** the return value of aac_open */
        NeAACDecHandle handle;
-       /** info about the currently decoded frame */
-       NeAACDecFrameInfo frame_info;
        /** whether this instance of the aac decoder is already initialized */
-       int initialized;
-       /**
-        * return value of aac_find_esds(). Used to call the right aacdec
-        * init function
-        */
-       unsigned long decoder_length;
+       bool initialized;
        /** number of times the decoder returned an error */
        unsigned error_count;
        /** number of bytes already consumed from the imput stream */
        size_t consumed_total;
-       /** return value of aac_find_entry_point */
-       size_t entry;
        /** The number of channels of the current stream. */
        unsigned int channels;
        /** Current sample rate in Hz. */
@@ -64,11 +51,18 @@ static int aacdec_execute(struct btr_node *btrn, const char *cmd, char **result)
 
 static void aacdec_open(struct filter_node *fn)
 {
+       NeAACDecConfigurationPtr c;
        struct private_aacdec_data *padd = para_calloc(sizeof(*padd));
 
+       padd->handle = NeAACDecOpen();
+       c = NeAACDecGetCurrentConfiguration(padd->handle);
+       c->defObjectType = LC;
+       c->outputFormat = FAAD_FMT_16BIT;
+       c->downMatrix = 0;
+       NeAACDecSetConfiguration(padd->handle, c);
+
        fn->private_data = padd;
        fn->min_iqs = 2048;
-       padd->handle = aac_open();
 }
 
 static void aacdec_close(struct filter_node *fn)
@@ -86,9 +80,9 @@ static int aacdec_post_select(__a_unused struct sched *s, void *context)
        struct btr_node *btrn = fn->btrn;
        struct private_aacdec_data *padd = fn->private_data;
        int i, ret;
-       char *p, *inbuf, *outbuffer;
-       char *btr_buf;
-       size_t len, skip, consumed, loaded;
+       char *inbuf, *outbuf, *btrbuf;
+       size_t len, consumed, loaded = 0;
+       NeAACDecFrameInfo frame_info;
 
 next_buffer:
        ret = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL);
@@ -103,121 +97,68 @@ next_buffer:
        if (!padd->initialized) {
                unsigned long rate = 0;
                unsigned char channels = 0;
-               ret = aac_find_esds(inbuf, len, &skip, &padd->decoder_length);
+               ret = NeAACDecInit(padd->handle, (unsigned char *)inbuf,
+                       len, &rate, &channels);
+               PARA_INFO_LOG("decoder init: %d\n", ret);
                if (ret < 0) {
-                       PARA_INFO_LOG("%s\n", para_strerror(-ret));
-                       ret = NeAACDecInit(padd->handle, (unsigned char *)inbuf,
-                               len, &rate, &channels);
-                       PARA_INFO_LOG("decoder init: %d\n", ret);
-                       if (ret < 0) {
-                               ret = -E_AACDEC_INIT;
-                               goto out;
-                       }
-                       consumed = ret;
-               } else {
-                       PARA_INFO_LOG("decoder len: %lu\n",
-                               padd->decoder_length);
-                       consumed += skip;
-                       p = inbuf + consumed;
                        ret = -E_AACDEC_INIT;
-                       if (NeAACDecInit2(padd->handle, (unsigned char *)p,
-                                       padd->decoder_length, &rate,
-                                       &channels) != 0)
-                               goto out;
+                       goto err;
                }
+               consumed = ret;
                padd->sample_rate = rate;
                padd->channels = channels;
                PARA_INFO_LOG("rate: %u, channels: %u\n",
                        padd->sample_rate, padd->channels);
-               padd->initialized = 1;
+               padd->initialized = true;
        }
-       if (padd->decoder_length > 0) {
-               consumed = 0;
-               if (!padd->entry) {
-                       ret = aac_find_entry_point(inbuf + consumed,
-                               len - consumed, &skip);
-                       if (ret < 0) {
-                               ret = len;
-                               goto out;
-                       }
-                       consumed += skip;
-                       padd->entry = ret;
-                       PARA_INFO_LOG("entry: %zu\n", padd->entry);
-               }
-               ret = len;
-               if (padd->consumed_total + len < padd->entry)
-                       goto out;
-               if (padd->consumed_total < padd->entry)
-                       consumed = padd->entry - padd->consumed_total;
-       }
-       for (; consumed < len; consumed++)
-               if ((inbuf[consumed] & 0xfe) == 0x20)
-                       break;
        if (consumed >= len)
                goto success;
-       p = inbuf + consumed;
        //PARA_CRIT_LOG("consumed: %zu (%zu + %zu), have: %zu\n", padd->consumed_total + consumed,
        //      padd->consumed_total, consumed, len - consumed);
-       outbuffer = NeAACDecDecode(padd->handle, &padd->frame_info,
-               (unsigned char *)p, len - consumed);
-       if (padd->frame_info.error) {
-               int err = padd->frame_info.error;
+       outbuf = NeAACDecDecode(padd->handle, &frame_info,
+               (unsigned char *)inbuf + consumed, len - consumed);
+       if (frame_info.error) {
+               int err = frame_info.error;
                ret = -E_AAC_DECODE;
                if (padd->error_count++ > MAX_ERRORS)
                        goto err;
-               /* Suppress non-fatal bitstream error message at BOF/EOF */
-               if (len < fn->min_iqs || padd->consumed_total == 0) {
-                       consumed = len;
-                       goto success;
-               }
-               PARA_ERROR_LOG("%s\n", NeAACDecGetErrorMessage(err));
-               PARA_ERROR_LOG("consumed: %zu + %zu + %lu\n",
+               PARA_NOTICE_LOG("error #%u: (%s)\n", padd->error_count,
+                       NeAACDecGetErrorMessage(err));
+               PARA_NOTICE_LOG("consumed (total, buffer, frame): "
+                       "%zu, %zu, %lu\n",
                        padd->consumed_total, consumed,
-                       padd->frame_info.bytesconsumed);
-               if (consumed < len)
-                       consumed++; /* catch 21 */
+                       frame_info.bytesconsumed);
+               consumed++; /* just eat one byte and hope for the best */
                goto success;
        }
        padd->error_count = 0;
-       //PARA_CRIT_LOG("decoder ate %lu\n", padd->frame_info.bytesconsumed);
-       consumed += padd->frame_info.bytesconsumed;
-       ret = consumed;
-       if (!padd->frame_info.samples)
-               goto out;
-       btr_buf = para_malloc(2 * padd->frame_info.samples);
-       loaded = 0;
-       for (i = 0; i < padd->frame_info.samples; i++) {
-               short sh = ((short *)outbuffer)[i];
-               write_int16_host_endian(btr_buf + loaded, sh);
+       //PARA_CRIT_LOG("decoder ate %lu\n", frame_info.bytesconsumed);
+       consumed += frame_info.bytesconsumed;
+       if (!frame_info.samples)
+               goto success;
+       btrbuf = para_malloc(2 * frame_info.samples);
+       for (i = 0; i < frame_info.samples; i++) {
+               short sh = ((short *)outbuf)[i];
+               write_int16_host_endian(btrbuf + loaded, sh);
                loaded += 2;
        }
-       btr_add_output(btr_buf, loaded, btrn);
+       btr_add_output(btrbuf, loaded, btrn);
 success:
-       ret = consumed;
-out:
-       if (ret >= 0) {
-               padd->consumed_total += ret;
-               btr_consume(btrn, ret);
+       btr_consume(btrn, consumed);
+       padd->consumed_total += consumed;
+       if (loaded == 0)
                goto next_buffer;
-       }
+       return 1;
 err:
        assert(ret < 0);
        btr_remove_node(&fn->btrn);
        return ret;
 }
 
-/**
- * The init function of the aacdec filter.
- *
- * \param f Pointer to the filter struct to initialize.
- *
- * \sa filter::init
- */
-void aacdec_filter_init(struct filter *f)
-{
-       f->open = aacdec_open;
-       f->close = aacdec_close;
-       f->pre_select = generic_filter_pre_select;
-       f->post_select = aacdec_post_select;
-       f->execute = aacdec_execute;
-}
+const struct filter lsg_filter_cmd_com_aacdec_user_data = {
+       .open = aacdec_open,
+       .close = aacdec_close,
+       .pre_select = generic_filter_pre_select,
+       .post_select = aacdec_post_select,
+       .execute = aacdec_execute
+};
diff --git a/acl.c b/acl.c
index 560ff99..2c90052 100644 (file)
--- a/acl.c
+++ b/acl.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file acl.c Access control lists for paraslash senders. */
 
@@ -35,10 +31,12 @@ struct access_info {
 /**
  * Return true if addr_1 matches addr_2 in the first `netmask' bits.
  */
-static int v4_addr_match(uint32_t addr_1, uint32_t addr_2, uint8_t netmask)
+static bool v4_addr_match(uint32_t addr_1, uint32_t addr_2, uint8_t netmask)
 {
        uint32_t mask = ~0U;
 
+       if (netmask == 0) /* avoid 32-bit shift, which is undefined in C. */
+               return true;
        if (netmask < 32)
                mask <<= (32 - netmask);
        return (htonl(addr_1) & mask) == (htonl(addr_2) & mask);
@@ -81,7 +79,7 @@ no_match:
  * \param addr The address to add.
  * \param netmask The netmask to use for this entry.
  */
-static void acl_add_entry(struct list_head *acl, char *addr, int netmask)
+void acl_add_entry(struct list_head *acl, char *addr, int netmask)
 {
        struct access_info *ai = para_malloc(sizeof(struct access_info));
 
@@ -103,14 +101,18 @@ static void acl_del_entry(struct list_head *acl, char *addr, unsigned netmask)
        struct access_info *ai, *tmp;
        struct in_addr to_delete;
 
+       PARA_NOTICE_LOG("removing entries matching %s/%u\n", addr, netmask);
        inet_pton(AF_INET, addr, &to_delete);
 
        list_for_each_entry_safe(ai, tmp, acl, node) {
-
                if (v4_addr_match(to_delete.s_addr, ai->addr.s_addr,
                                        PARA_MIN(netmask, ai->netmask))) {
-                       PARA_NOTICE_LOG("removing %s/%u from access list\n",
-                                       addr, ai->netmask);
+                       char dst[INET_ADDRSTRLEN + 1];
+                       const char *p = inet_ntop(AF_INET, &ai->addr.s_addr,
+                               dst, sizeof(dst));
+                       if (p)
+                               PARA_INFO_LOG("removing %s/%u\n", p,
+                                       ai->netmask);
                        list_del(&ai->node);
                        free(ai);
                }
@@ -139,27 +141,6 @@ char *acl_get_contents(struct list_head *acl)
        return ret;
 }
 
-/**
- * Initialize an access control list.
- *
- * \param acl The list to initialize.
- * \param acl_info An array of strings of the form ip/netmask.
- * \param num The number of strings in \a acl_info.
- */
-void acl_init(struct list_head *acl, char * const *acl_info, int num)
-{
-       char    addr[16];
-       int     mask, i;
-
-       INIT_LIST_HEAD(acl);
-       for (i = 0; i < num; i++)
-               if (parse_cidr(acl_info[i], addr, sizeof(addr), &mask) == NULL)
-                       PARA_CRIT_LOG("ACL syntax error: %s, ignoring\n",
-                                     acl_info[i]);
-               else
-                       acl_add_entry(acl, addr, mask);
-}
-
 /**
  * Check whether the peer name of a given fd is allowed by an acl.
  *
diff --git a/acl.h b/acl.h
index 5bfa39f..02002e5 100644 (file)
--- a/acl.h
+++ b/acl.h
@@ -1,12 +1,8 @@
-/*
- * Copyright (C) 2008 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2008 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file acl.h Exported functions of acl.c. */
 
-void acl_init(struct list_head *acl, char * const *acl_info, int num);
+void acl_add_entry(struct list_head *acl, char *addr, int netmask);
 char *acl_get_contents(struct list_head *acl);
 int acl_check_access(int fd, struct list_head *acl, int default_deny);
 void acl_allow(char *addr, int mask, struct list_head *acl, int default_deny);
diff --git a/afh.c b/afh.c
index 36c432e..c896a7d 100644 (file)
--- a/afh.c
+++ b/afh.c
@@ -1,26 +1,28 @@
-/*
- * Copyright (C) 2008 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2008 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file afh.c Paraslash's standalone audio format handler tool. */
 
 #include <regex.h>
+#include <lopsub.h>
 
+#include "afh.lsg.h"
 #include "para.h"
 #include "string.h"
-#include "afh.cmdline.h"
 #include "fd.h"
 #include "afh.h"
 #include "error.h"
 #include "version.h"
-#include "ggo.h"
 
 /** Array of error strings. */
 DEFINE_PARA_ERRLIST;
 
-static struct afh_args_info conf;
+static struct lls_parse_result *lpr;
+
+#define CMD_PTR (lls_cmd(0, afh_suite))
+#define OPT_RESULT(_name) (lls_opt_result(LSG_AFH_PARA_AFH_OPT_ ## _name, lpr))
+#define OPT_GIVEN(_name) (lls_opt_given(OPT_RESULT(_name)))
+#define OPT_STRING_VAL(_name) (lls_string_val(0, OPT_RESULT(_name)))
+#define OPT_UINT32_VAL(_name) (lls_uint32_val(0, OPT_RESULT(_name)))
 
 static int loglevel;
 INIT_STDERR_LOGGING(loglevel)
@@ -37,34 +39,38 @@ static int rewrite_tags(const char *name, int input_fd, void *map,
        struct taginfo *tags = &afhi->tags;
        bool modified = false;
        char *tmp_name;
+       const char *arg;
        int output_fd = -1, ret;
        struct stat sb;
 
-       if (tag_needs_update(conf.year_given, tags->year, conf.year_arg)) {
+       arg = OPT_STRING_VAL(YEAR);
+       if (tag_needs_update(OPT_GIVEN(YEAR), tags->year, arg)) {
                free(tags->year);
-               tags->year = para_strdup(conf.year_arg);
+               tags->year = para_strdup(arg);
                modified = true;
        }
-       if (tag_needs_update(conf.title_given, tags->title, conf.title_arg)) {
+       arg = OPT_STRING_VAL(TITLE);
+       if (tag_needs_update(OPT_GIVEN(TITLE), tags->title, arg)) {
                free(tags->title);
-               tags->title = para_strdup(conf.title_arg);
+               tags->title = para_strdup(arg);
                modified = true;
        }
-       if (tag_needs_update(conf.artist_given, tags->artist,
-                       conf.artist_arg)) {
+       arg = OPT_STRING_VAL(ARTIST);
+       if (tag_needs_update(OPT_GIVEN(ARTIST), tags->artist, arg)) {
                free(tags->artist);
-               tags->artist = para_strdup(conf.artist_arg);
+               tags->artist = para_strdup(arg);
                modified = true;
        }
-       if (tag_needs_update(conf.album_given, tags->album, conf.album_arg)) {
+       arg = OPT_STRING_VAL(ALBUM);
+       if (tag_needs_update(OPT_GIVEN(ALBUM), tags->album, arg)) {
                free(tags->album);
-               tags->album = para_strdup(conf.album_arg);
+               tags->album = para_strdup(arg);
                modified = true;
        }
-       if (tag_needs_update(conf.comment_given, tags->comment,
-                       conf.comment_arg)) {
+       arg = OPT_STRING_VAL(COMMENT);
+       if (tag_needs_update(OPT_GIVEN(COMMENT), tags->comment, arg)) {
                free(tags->comment);
-               tags->comment = para_strdup(conf.comment_arg);
+               tags->comment = para_strdup(arg);
                modified = true;
        }
        if (!modified) {
@@ -99,7 +105,7 @@ static int rewrite_tags(const char *name, int input_fd, void *map,
                tmp_name);
        if (ret < 0)
                goto out;
-       if (conf.backup_given) {
+       if (OPT_GIVEN(BACKUP)) {
                char *backup_name = make_message("%s~", name);
                ret = xrename(name, backup_name);
                free(backup_name);
@@ -107,6 +113,24 @@ static int rewrite_tags(const char *name, int input_fd, void *map,
                        goto out;
        }
        ret = xrename(tmp_name, name);
+       if (ret < 0)
+               goto out;
+       if (OPT_GIVEN(PRESERVE)) {
+               struct timespec times[2]; /* [0]: atime, [1]: mtime */
+               times[0].tv_nsec = UTIME_OMIT;
+               times[1] = sb.st_mtim;
+               /*
+                * We might well have written a file of identical size. If we
+                * keep the mtime as well, we might fool backup applications
+                * like rsync which skip files whose size and mtime haven't
+                * changed. So we change the mtime slightly.
+                */
+               times[1].tv_sec++;
+               if (futimens(output_fd, times) < 0) {
+                       ret = -ERRNO_TO_PARA_ERROR(errno);
+                       goto out;
+               }
+       }
 out:
        if (ret < 0 && output_fd >= 0)
                unlink(tmp_name); /* ignore errors */
@@ -125,39 +149,53 @@ static void print_info(int audio_format_num, struct afh_info *afhi)
        free(msg);
 }
 
-static void print_chunk_table(struct afh_info *afhi)
+static void print_chunk_table(struct afh_info *afhi, int audio_format_id,
+               const void *map, size_t mapsize)
 {
-       int i;
+       int i, ret;
+       void *ctx = NULL;
 
-       if (conf.parser_friendly_given) {
-               printf("chunk_table: ");
-               for (i = 0; i <= afhi->chunks_total; i++)
-                       printf("%u ", afhi->chunk_table[i]);
-               printf("\n");
-               return;
-       }
-       for (i = 1; i <= afhi->chunks_total; i++) {
+       for (i = 0; i < afhi->chunks_total; i++) {
                struct timeval tv;
                long unsigned from, to;
-               tv_scale(i - 1, &afhi->chunk_tv, &tv);
-               from = tv2ms(&tv);
+               const char *buf;
+               size_t len;
                tv_scale(i, &afhi->chunk_tv, &tv);
+               from = tv2ms(&tv);
+               tv_scale(i + 1, &afhi->chunk_tv, &tv);
                to = tv2ms(&tv);
-               printf("%d [%lu.%03lu - %lu.%03lu] %u - %u (%u)\n", i - 1,
-                       from / 1000, from % 1000, to / 1000, to % 1000,
-                       afhi->chunk_table[i - 1], afhi->chunk_table[i],
-                       afhi->chunk_table[i] - afhi->chunk_table[i - 1]);
+               ret = afh_get_chunk(i, afhi, audio_format_id, map, mapsize,
+                       &buf, &len, &ctx);
+               if (ret < 0) {
+                       PARA_ERROR_LOG("fatal: chunk %d: %s\n", i,
+                               para_strerror(-ret));
+                       return;
+               }
+               if (!OPT_GIVEN(PARSER_FRIENDLY))
+                       printf("%d [%lu.%03lu - %lu.%03lu] ", i, from / 1000,
+                               from % 1000, to / 1000, to % 1000);
+               printf("%td - %td", buf - (const char *)map,
+                       buf + len - (const char *)map);
+               if (!OPT_GIVEN(PARSER_FRIENDLY))
+                       printf(" (%zu)", len);
+               printf("\n");
        }
+       afh_close(ctx, audio_format_id);
 }
 
-__noreturn static void print_help_and_die(void)
+static void handle_help_flags(void)
 {
-       struct ggo_help h = DEFINE_GGO_HELP(afh);
-       int d = conf.detailed_help_given;
-       unsigned flags = d? GPH_STANDARD_FLAGS_DETAILED : GPH_STANDARD_FLAGS;
+       char *help;
 
-       ggo_print_help(&h, flags);
-       printf("supported audio formats: %s\n", AUDIO_FORMAT_HANDLERS);
+       if (OPT_GIVEN(DETAILED_HELP))
+               help = lls_long_help(CMD_PTR);
+       else if (OPT_GIVEN(HELP) || lls_num_inputs(lpr) == 0)
+               help = lls_short_help(CMD_PTR);
+       else
+               return;
+       printf("%s", help);
+       free(help);
+       printf("Supported audio formats\n  %s\n", AUDIO_FORMAT_HANDLERS);
        exit(EXIT_SUCCESS);
 }
 
@@ -175,34 +213,36 @@ int main(int argc, char **argv)
        void *audio_file_data;
        size_t audio_file_size;
        struct afh_info afhi;
+       char *errctx;
 
-       afh_cmdline_parser(argc, argv, &conf);
-       loglevel = get_loglevel_by_name(conf.loglevel_arg);
-       version_handle_flag("afh", conf.version_given);
-       if (conf.help_given || conf.detailed_help_given || conf.inputs_num == 0)
-               print_help_and_die();
-       afh_init();
-       for (i = 0; i < conf.inputs_num; i++) {
+       ret = lls(lls_parse(argc, argv, CMD_PTR, &lpr, &errctx));
+       if (ret < 0)
+               goto out;
+       loglevel = OPT_UINT32_VAL(LOGLEVEL);
+       version_handle_flag("afh", OPT_GIVEN(VERSION));
+       handle_help_flags();
+       for (i = 0; i < lls_num_inputs(lpr); i++) {
                int ret2;
-               ret = mmap_full_file(conf.inputs[i], O_RDONLY, &audio_file_data,
+               const char *path = lls_input(i, lpr);
+               ret = mmap_full_file(path, O_RDONLY, &audio_file_data,
                        &audio_file_size, &fd);
                if (ret < 0) {
-                       PARA_ERROR_LOG("failed to mmap \"%s\"\n", conf.inputs[i]);
+                       PARA_ERROR_LOG("failed to mmap \"%s\"\n", path);
                        goto out;
                }
-               ret = compute_afhi(conf.inputs[i], audio_file_data, audio_file_size,
+               ret = compute_afhi(path, audio_file_data, audio_file_size,
                        fd, &afhi);
                if (ret >= 0) {
                        audio_format_num = ret;
-                       if (conf.modify_given) {
-                               ret = rewrite_tags(conf.inputs[i], fd, audio_file_data,
+                       if (OPT_GIVEN(MODIFY)) {
+                               ret = rewrite_tags(path, fd, audio_file_data,
                                        audio_file_size, audio_format_num, &afhi);
                        } else {
-                               printf("File %d: %s\n", i + 1, conf.inputs[i]);
+                               printf("File %d: %s\n", i + 1, path);
                                print_info(audio_format_num, &afhi);
-                               if (conf.chunk_table_given)
-                                       print_chunk_table(&afhi);
-                               printf("\n");
+                               if (OPT_GIVEN(CHUNK_TABLE))
+                                       print_chunk_table(&afhi, audio_format_num,
+                                               audio_file_data, audio_file_size);
                        }
                        clear_afhi(&afhi);
                }
@@ -214,7 +254,10 @@ int main(int argc, char **argv)
                        break;
        }
 out:
+       if (errctx)
+               PARA_ERROR_LOG("%s\n", errctx);
        if (ret < 0)
-               PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+               PARA_EMERG_LOG("%s\n", para_strerror(-ret));
+       lls_free_parse_result(lpr, CMD_PTR);
        return ret < 0? EXIT_FAILURE : EXIT_SUCCESS;
 }
diff --git a/afh.h b/afh.h
index a6f9c50..b3295f6 100644 (file)
--- a/afh.h
+++ b/afh.h
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file afh.h Structures for audio format handling (para_server). */
 
@@ -40,6 +36,8 @@ struct afh_info {
         * the current audio file.
         */
        uint32_t *chunk_table;
+       /** Size of the largest chunk, introduced in v0.6.0. */
+       uint32_t max_chunk_size;
        /** Period of time between sending data chunks. */
        struct timeval chunk_tv;
        /**
@@ -60,11 +58,12 @@ struct afh_info {
 
 /** Data about the current audio file, passed from afs to server. */
 struct audio_file_data {
-       /** The open file descriptor to the current audio file. */
-       int fd;
        /** Vss needs this for streaming. */
        struct afh_info afhi;
-       /** Size of the largest chunk. */
+       /**
+        * Size of the largest chunk. Superseded by afhi->max_chunk_size. May
+        * be removed after v0.6.1.
+        */
        uint32_t max_chunk_size;
        /** Needed to get the audio file header. */
        uint8_t audio_format_id;
@@ -79,14 +78,6 @@ struct audio_file_data {
  * in the other part of this struct.
  */
 struct audio_format_handler {
-       /** 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)(struct audio_format_handler*);
        /** Typical file endings for files that can be handled by this afh. */
        const char * const *suffixes;
        /**
@@ -98,12 +89,32 @@ struct audio_format_handler {
         * success, the function must return a positive value and fill in the
         * given struct afh_info.
         *
-        * \sa struct afh_info
+        * \sa struct \ref afh_info.
         */
        int (*get_file_info)(char *map, size_t numbytes, int fd,
-               struct afh_info *afi);
+               struct afh_info *afhi);
        /** Optional, used for header-rewriting. See \ref afh_get_header(). */
        void (*get_header)(void *map, size_t mapsize, char **buf, size_t *len);
+       /**
+        * An audio format handler may signify support for dynamic chunks by
+        * defining ->get_chunk below. In this case the vss calls ->open() at
+        * BOS, ->get_chunk() for each chunk while streaming, and ->close() at
+        * EOS. The chunk table is not accessed at all.
+        *
+        * The function may return its (opaque) context through the last
+        * argument. The returned pointer is passed to subsequent calls to
+        * ->get_chunk() and ->close().
+        */
+       int (*open)(const void *map, size_t mapsize, void **afh_context);
+       /**
+        * Return a reference to one chunk. The returned pointer points to a
+        * portion of the memory mapped audio file. The caller must not call
+        * free() on it.
+        */
+       int (*get_chunk)(long unsigned chunk_num, void *afh_context,
+               const char **buf, size_t *len);
+       /** Deallocate the resources occupied by ->open(). */
+       void (*close)(void *afh_context);
        /**
         * Write audio file with altered tags, optional.
         *
@@ -114,15 +125,16 @@ struct audio_format_handler {
                int output_fd, const char *filename);
 };
 
-void afh_init(void);
 int guess_audio_format(const char *name);
 int compute_afhi(const char *path, char *data, size_t size,
        int fd, struct afh_info *afhi);
 const char *audio_format_name(int);
-void afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi,
-               void *map, const char **buf, size_t *len);
+__must_check int afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi,
+               uint8_t audio_format_id, const void *map, size_t mapsize,
+               const char **buf, size_t *len, void **afh_context);
+void afh_close(void *afh_context, uint8_t audio_format_id);
 int32_t afh_get_start_chunk(int32_t approx_chunk_num,
-               const struct afh_info *afhi);
+               const struct afh_info *afhi, uint8_t audio_format_id);
 void afh_get_header(struct afh_info *afhi, uint8_t audio_format_id,
                void *map, size_t mapsize, char **buf, size_t *len);
 void afh_free_header(char *header_buf, uint8_t audio_format_id);
@@ -130,3 +142,5 @@ void clear_afhi(struct afh_info *afhi);
 unsigned afh_get_afhi_txt(int audio_format_num, struct afh_info *afhi, char **result);
 int afh_rewrite_tags(int audio_format_id, void *map, size_t mapsize,
                struct taginfo *tags, int output_fd, const char *filename);
+void set_max_chunk_size(struct afh_info *afhi);
+bool afh_supports_dynamic_chunks(int audio_format_id);
index dfbf751..a267f58 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 1997 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 1997 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file afh_common.c Common audio format handler functions. */
 
 #include "string.h"
 #include "afh.h"
 
-typedef void afh_init_func(struct audio_format_handler *);
-/* It does not hurt to declare init functions which are not available. */
-extern afh_init_func mp3_init, ogg_init, aac_afh_init, wma_afh_init,
-       spx_afh_init, flac_afh_init, opus_afh_init;
-
 /** The list of all status items */
-const char *status_item_list[] = {STATUS_ITEM_ARRAY};
+const char *status_item_list[] = {STATUS_ITEMS};
 
 /**
- * The list of supported audio formats.
+ * For each audio file the number of its audio format is stored in the
+ * database. Therefore this list, in particular its order, is part of the ABI.
+ * So it's only OK to append new audio formats. All audio formats are listed
+ * here, regardless of whether the audio format handler is compiled in.
+ */
+#define ALL_AUDIO_FORMATS \
+       AUDIO_FORMAT(mp3) \
+       AUDIO_FORMAT(ogg) \
+       AUDIO_FORMAT(aac) \
+       AUDIO_FORMAT(wma) \
+       AUDIO_FORMAT(spx) \
+       AUDIO_FORMAT(flac) \
+       AUDIO_FORMAT(opus) \
+
+/** \cond audio_format_handler */
+#define AUDIO_FORMAT(_fmt) #_fmt,
+static const char * const audio_format_names[] = {ALL_AUDIO_FORMATS};
+#undef AUDIO_FORMAT
+/* Weak declarations must be public. */
+#define AUDIO_FORMAT(_fmt) \
+       struct audio_format_handler _fmt ## _afh __attribute__ ((weak)) \
+       = {.get_file_info = NULL};
+ALL_AUDIO_FORMATS
+#undef AUDIO_FORMAT
+#define AUDIO_FORMAT(_fmt) & _fmt ## _afh,
+static struct audio_format_handler *afl[] = {ALL_AUDIO_FORMATS};
+#undef AUDIO_FORMAT
+#define NUM_AUDIO_FORMATS (ARRAY_SIZE(afl))
+/** \endcond audio_format_handler */
+
+/**
+ * Get the name of the given audio format.
  *
- * We always define the full array of audio formats even if some audio formats
- * were not compiled in. This is because for each audio file the number of its
- * audio format is stored in the database. We don't want that numbers to become
- * stale just because the user installed a new version of paraslash that
- * supports a different set of audio formats.
+ * \param i The audio format number.
  *
- * It can still be easily detected whether an audio format is compiled in by
- * checking if the init function pointer is not \p NULL.
+ * \return This returns a pointer to statically allocated memory so it
+ * must not be freed by the caller.
  */
-static struct audio_format_handler afl[] = {
-       {
-               .name = "mp3",
-               .init = mp3_init,
-       },
-       {
-               .name = "ogg",
-#if defined(HAVE_OGG) && defined(HAVE_VORBIS)
-               .init = ogg_init,
-#endif
-       },
-       {
-               .name = "aac",
-#if defined(HAVE_MP4V2)
-               .init = aac_afh_init,
-#endif
-       },
-       {
-               .name = "wma",
-               .init = wma_afh_init,
-       },
-       {
-               .name = "spx",
-#if defined(HAVE_OGG) && defined(HAVE_SPEEX)
-               .init = spx_afh_init,
-#endif
-       },
-       {
-               .name = "flac",
-#if defined(HAVE_OGG) && defined(HAVE_FLAC)
-               .init = flac_afh_init,
-#endif
-       },
-       {
-               .name = "opus",
-#if defined(HAVE_OGG) && defined(HAVE_OPUS)
-               .init = opus_afh_init,
-#endif
-       },
-       {
-               .name = NULL,
-       }
-};
+const char *audio_format_name(int i)
+{
+       if (i < 0 || i >= NUM_AUDIO_FORMATS)
+               return "???";
+       return audio_format_names[i];
+}
 
 static inline int next_audio_format(int format)
 {
        for (;;) {
-               if (!afl[format].name)
-                       return format;
                format++;
-               if (afl[format].init)
+               if (format >= NUM_AUDIO_FORMATS)
+                       return format;
+               if (afl[format]->get_file_info)
                        return format;
        }
-
 }
 
 /** Iterate over each supported audio format. */
-#define FOR_EACH_AUDIO_FORMAT(i) for (i = 0; afl[i].name; i = next_audio_format(i))
+#define FOR_EACH_AUDIO_FORMAT(i) \
+       for (i = 0; i < NUM_AUDIO_FORMATS; i = next_audio_format(i))
 
 /**
- * Call the init function of each supported audio format handler.
+ * Tell whether an audio format handler provides chunk tables.
+ *
+ * Each audio format handler either provides a chunk table or supports dynamic
+ * chunks.
+ *
+ * \param audio_format_id Offset in the afl array.
+ *
+ * \return True if dynamic chunks are supported, false if the audio format
+ * handler provides chunk tables.
  */
-void afh_init(void)
+bool afh_supports_dynamic_chunks(int audio_format_id)
 {
-       int i;
-
-       PARA_NOTICE_LOG("supported audio formats: %s\n", AUDIO_FORMAT_HANDLERS);
-       FOR_EACH_AUDIO_FORMAT(i) {
-               PARA_INFO_LOG("initializing %s handler\n",
-                       audio_format_name(i));
-               afl[i].init(&afl[i]);
-       }
+       return afl[audio_format_id]->get_chunk;
 }
 
 /**
@@ -123,8 +105,8 @@ int guess_audio_format(const char *name)
        int i,j, len = strlen(name);
 
        FOR_EACH_AUDIO_FORMAT(i) {
-               for (j = 0; afl[i].suffixes[j]; j++) {
-                       const char *p = afl[i].suffixes[j];
+               for (j = 0; afl[i]->suffixes[j]; j++) {
+                       const char *p = afl[i]->suffixes[j];
                        int plen = strlen(p);
                        if (len < plen + 1)
                                continue;
@@ -139,21 +121,6 @@ int guess_audio_format(const char *name)
        return -E_AUDIO_FORMAT;
 }
 
-/**
- * Get the name of the given audio format.
- *
- * \param i The audio format number.
- *
- * \return This returns a pointer to statically allocated memory so it
- * must not be freed by the caller.
- */
-const char *audio_format_name(int i)
-{
-       if (i < 0 || i >= ARRAY_SIZE(afl) - 1)
-               return "???";
-       return afl[i].name;
-}
-
 static int get_file_info(int format, const char *path, char *data,
                size_t size, int fd, struct afh_info *afhi)
 {
@@ -161,7 +128,7 @@ static int get_file_info(int format, const char *path, char *data,
        const char *fmt = audio_format_name(format);
 
        memset(afhi, 0, sizeof(*afhi));
-       ret = afl[format].get_file_info(data, size, fd, afhi);
+       ret = afl[format]->get_file_info(data, size, fd, afhi);
        if (ret < 0) {
                PARA_WARNING_LOG("%s: %s format not detected: %s\n",
                        path, fmt, para_strerror(-ret));
@@ -261,21 +228,73 @@ static inline size_t get_chunk_len(long unsigned chunk_num,
 /**
  * Get one chunk of audio data.
  *
+ * This implicitly calls the ->open method of the audio format handler at the
+ * first call.
+ *
  * \param chunk_num The number of the chunk to get.
  * \param afhi Describes the audio file.
+ * \param audio_format_id Determines the afh.
  * \param map The memory mapped audio file.
+ * \param mapsize Passed to the afh's ->open() method.
  * \param buf Result pointer.
  * \param len The length of the chunk in bytes.
+ * \param afh_context Value/result, determines whether ->open() is called.
  *
  * Upon return, \a buf will point so memory inside \a map. The returned buffer
  * must therefore not be freed by the caller.
+ *
+ * \return Standard.
  */
-void afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi,
-               void *map, const char **buf, size_t *len)
+__must_check int afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi,
+               uint8_t audio_format_id, const void *map, size_t mapsize,
+               const char **buf, size_t *len, void **afh_context)
 {
-       size_t pos = afhi->chunk_table[chunk_num];
-       *buf = map + pos;
-       *len = get_chunk_len(chunk_num, afhi);
+       struct audio_format_handler *afh = afl[audio_format_id];
+
+       if (afh_supports_dynamic_chunks(audio_format_id)) {
+               int ret;
+
+               if (!*afh_context) {
+                       ret = afh->open(map, mapsize, afh_context);
+                       if (ret < 0)
+                               return ret;
+               }
+               ret = afh->get_chunk(chunk_num, *afh_context,
+                       buf, len);
+               if (ret < 0) {
+                       afh->close(*afh_context);
+                       *afh_context = NULL;
+               }
+               return ret;
+       } else {
+               size_t pos = afhi->chunk_table[chunk_num];
+               *buf = map + pos;
+               *len = get_chunk_len(chunk_num, afhi);
+               return 0;
+       }
+}
+
+/**
+ * Deallocate resources allocated due to dynamic chunk handling.
+ *
+ * This function should be called if afh_get_chunk() was called at least once.
+ * It is OK to call it even for audio formats which do not support dynamic
+ * chunks, in which case the function does nothing.
+ *
+ * \param afh_context As returned from the ->open method of the afh.
+ * \param audio_format_id Determines the afh.
+ */
+void afh_close(void *afh_context, uint8_t audio_format_id)
+{
+       struct audio_format_handler *afh = afl[audio_format_id];
+
+       if (!afh_supports_dynamic_chunks(audio_format_id))
+               return;
+       if (!afh->close)
+               return;
+       if (!afh_context)
+               return;
+       afh->close(afh_context);
 }
 
 /**
@@ -283,16 +302,22 @@ void afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi,
  *
  * \param approx_chunk_num Upper bound for the chunk number to return.
  * \param afhi Needed for the chunk table.
+ * \param audio_format_id Determines the afh.
  *
- * \return The first non-empty chunk <= \a approx_chunk_num.
+ * \return For audio format handlers which support dynamic chunks, the function
+ * returns the given chunk number. Otherwise it returns the first non-empty
+ * chunk <= \a approx_chunk_num.
  *
  * \sa \ref afh_get_chunk().
  */
 int32_t afh_get_start_chunk(int32_t approx_chunk_num,
-               const struct afh_info *afhi)
+               const struct afh_info *afhi, uint8_t audio_format_id)
 {
        int32_t k;
 
+       if (afh_supports_dynamic_chunks(audio_format_id))
+               return approx_chunk_num;
+
        for (k = PARA_MAX(0, approx_chunk_num); k >= 0; k--)
                if (get_chunk_len(k, afhi) > 0)
                        return k;
@@ -324,7 +349,7 @@ int32_t afh_get_start_chunk(int32_t approx_chunk_num,
 void afh_get_header(struct afh_info *afhi, uint8_t audio_format_id,
                void *map, size_t mapsize, char **buf, size_t *len)
 {
-       struct audio_format_handler *afh = afl + audio_format_id;
+       struct audio_format_handler *afh = afl[audio_format_id];
 
        if (!map || !afhi || !afhi->header_len) {
                *buf = NULL;
@@ -347,7 +372,7 @@ void afh_get_header(struct afh_info *afhi, uint8_t audio_format_id,
  */
 void afh_free_header(char *header_buf, uint8_t audio_format_id)
 {
-       struct audio_format_handler *afh = afl + audio_format_id;
+       struct audio_format_handler *afh = afl[audio_format_id];
 
        if (afh->get_header)
                free(header_buf);
@@ -374,29 +399,62 @@ unsigned afh_get_afhi_txt(int audio_format_num, struct afh_info *afhi, char **re
                "%s: %" PRIu32 "\n" /* seconds total */
                "%s: %lu: %lu\n" /* chunk time */
                "%s: %" PRIu32 "\n" /* num chunks */
+               "%s: %" PRIu32 "\n" /* max chunk size */
                "%s: %s\n" /* techinfo */
                "%s: %s\n" /* artist */
                "%s: %s\n" /* title */
                "%s: %s\n" /* year */
                "%s: %s\n" /* album */
                "%s: %s\n", /* comment */
-               status_item_list[SI_BITRATE], afhi->bitrate,
-               status_item_list[SI_FORMAT], audio_format_name(audio_format_num),
-               status_item_list[SI_FREQUENCY], afhi->frequency,
-               status_item_list[SI_CHANNELS], afhi->channels,
-               status_item_list[SI_SECONDS_TOTAL], afhi->seconds_total,
-               status_item_list[SI_CHUNK_TIME], (long unsigned)afhi->chunk_tv.tv_sec,
+               status_item_list[SI_bitrate], afhi->bitrate,
+               status_item_list[SI_format], audio_format_name(audio_format_num),
+               status_item_list[SI_frequency], afhi->frequency,
+               status_item_list[SI_channels], afhi->channels,
+               status_item_list[SI_seconds_total], afhi->seconds_total,
+               status_item_list[SI_chunk_time], (long unsigned)afhi->chunk_tv.tv_sec,
                        (long unsigned)afhi->chunk_tv.tv_usec,
-               status_item_list[SI_NUM_CHUNKS], afhi->chunks_total,
-               status_item_list[SI_TECHINFO], afhi->techinfo? afhi->techinfo : "",
-               status_item_list[SI_ARTIST], afhi->tags.artist? afhi->tags.artist : "",
-               status_item_list[SI_TITLE], afhi->tags.title? afhi->tags.title : "",
-               status_item_list[SI_YEAR], afhi->tags.year? afhi->tags.year : "",
-               status_item_list[SI_ALBUM], afhi->tags.album? afhi->tags.album : "",
-               status_item_list[SI_COMMENT], afhi->tags.comment? afhi->tags.comment : ""
+               status_item_list[SI_num_chunks], afhi->chunks_total,
+               status_item_list[SI_max_chunk_size], afhi->max_chunk_size,
+               status_item_list[SI_techinfo], afhi->techinfo? afhi->techinfo : "",
+               status_item_list[SI_artist], afhi->tags.artist? afhi->tags.artist : "",
+               status_item_list[SI_title], afhi->tags.title? afhi->tags.title : "",
+               status_item_list[SI_year], afhi->tags.year? afhi->tags.year : "",
+               status_item_list[SI_album], afhi->tags.album? afhi->tags.album : "",
+               status_item_list[SI_comment], afhi->tags.comment? afhi->tags.comment : ""
        );
 }
 
+/**
+ * Determine the maximal chunk size by investigating the chunk table.
+ *
+ * \param afhi Value/result.
+ *
+ * This function iterates over the chunk table and sets ->max_chunk_size
+ * accordingly. The function exists only for backward compatibility since as of
+ * version 0.6.0, para_server stores the maximal chunk size in its database.
+ * This function is only called if the database value is zero, indicating that
+ * the file was added by an older server version.
+ */
+void set_max_chunk_size(struct afh_info *afhi)
+{
+       uint32_t n, max = 0, old = 0;
+
+       for (n = 0; n <= afhi->chunks_total; n++) {
+               uint32_t val = afhi->chunk_table[n];
+               /*
+                * If the first chunk is the header, do not consider it for the
+                * calculation of the largest chunk size.
+                */
+               if (n == 0 || (n == 1 && afhi->header_len > 0)) {
+                       old = val;
+                       continue;
+               }
+               max = PARA_MAX(max, val - old);
+               old = val;
+       }
+       afhi->max_chunk_size = max;
+}
+
 /**
  * Create a copy of the given file with altered meta tags.
  *
@@ -418,7 +476,7 @@ unsigned afh_get_afhi_txt(int audio_format_num, struct afh_info *afhi, char **re
 int afh_rewrite_tags(int audio_format_id, void *map, size_t mapsize,
                struct taginfo *tags, int output_fd, const char *filename)
 {
-       struct audio_format_handler *afh = afl + audio_format_id;
+       struct audio_format_handler *afh = afl[audio_format_id];
 
        if (!afh->rewrite_tags)
                return -ERRNO_TO_PARA_ERROR(ENOTSUP);
index 28d8f39..4f8ff49 100644 (file)
@@ -1,22 +1,18 @@
-/*
- * Copyright (C) 2011 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2011 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file afh_recv.c Receiver for streaming local files. */
 
 #include <regex.h>
 #include <sys/types.h>
+#include <lopsub.h>
 
+#include "recv_cmd.lsg.h"
 #include "para.h"
 #include "error.h"
 #include "list.h"
 #include "sched.h"
-#include "ggo.h"
 #include "buffer_tree.h"
 #include "recv.h"
-#include "afh_recv.cmdline.h"
 #include "string.h"
 #include "fd.h"
 #include "afh.h"
@@ -31,6 +27,7 @@ struct private_afh_recv_data {
        long unsigned last_chunk;
        struct timeval stream_start;
        uint32_t current_chunk;
+       void *afh_context;
 };
 
 static int afh_execute(struct btr_node *btrn, const char *cmd, char **result)
@@ -58,80 +55,71 @@ static int afh_execute(struct btr_node *btrn, const char *cmd, char **result)
                        return ret;
                if (x >= pard->afhi.chunks_total)
                        return -ERRNO_TO_PARA_ERROR(EINVAL);
-               pard->first_chunk = afh_get_start_chunk(x, &pard->afhi);
+               pard->first_chunk = afh_get_start_chunk(x, &pard->afhi,
+                       pard->audio_format_num);
                pard->current_chunk = pard->first_chunk;
                return 1;
        }
        return -E_BTR_NAVAIL;
 }
 
-static void *afh_recv_parse_config(int argc, char **argv)
-{
-       struct afh_recv_args_info *tmp = para_calloc(sizeof(*tmp));
-
-       afh_recv_cmdline_parser(argc, argv, tmp);
-       return tmp;
-}
-
-static void afh_recv_free_config(void *conf)
-{
-       if (!conf)
-               return;
-       afh_recv_cmdline_parser_free(conf);
-       free(conf);
-}
-
 static int afh_recv_open(struct receiver_node *rn)
 {
-       struct afh_recv_args_info *conf = rn->conf;
+       struct lls_parse_result *lpr = rn->lpr;
        struct private_afh_recv_data *pard;
        struct afh_info *afhi;
-       char *filename = conf->filename_arg;
-
+       const char *fn = RECV_CMD_OPT_STRING_VAL(AFH, FILENAME, lpr), *msg;
+       int32_t bc = RECV_CMD_OPT_INT32_VAL(AFH, BEGIN_CHUNK, lpr);
+       const struct lls_opt_result *r_e = RECV_CMD_OPT_RESULT(AFH, END_CHUNK, lpr);
        int ret;
 
-       if (!filename || *filename == '\0')
+       if (!fn || *fn == '\0')
                return -E_AFH_RECV_BAD_FILENAME;
        rn->private_data = pard = para_calloc(sizeof(*pard));
        afhi = &pard->afhi;
-       ret = mmap_full_file(filename, O_RDONLY, &pard->map,
+       ret = mmap_full_file(fn, O_RDONLY, &pard->map,
                &pard->map_size, &pard->fd);
        if (ret < 0)
                goto out;
-       ret = compute_afhi(filename, pard->map, pard->map_size,
+       ret = compute_afhi(fn, pard->map, pard->map_size,
                pard->fd, afhi);
        if (ret < 0)
                goto out_unmap;
        pard->audio_format_num = ret;
        ret = -ERRNO_TO_PARA_ERROR(EINVAL);
+       msg = "no data chunks";
        if (afhi->chunks_total == 0)
                goto out_clear_afhi;
-       if (PARA_ABS(conf->begin_chunk_arg) >= afhi->chunks_total)
+       msg = "invalid begin chunk";
+       if (PARA_ABS(bc) >= afhi->chunks_total)
                goto out_clear_afhi;
-       if (conf->begin_chunk_arg >= 0)
-               pard->first_chunk = afh_get_start_chunk(
-                       conf->begin_chunk_arg, &pard->afhi);
+       if (bc >= 0)
+               pard->first_chunk = afh_get_start_chunk(bc, &pard->afhi,
+                       pard->audio_format_num);
        else
-               pard->first_chunk = afh_get_start_chunk(
-                       afhi->chunks_total + conf->begin_chunk_arg,
-                       &pard->afhi);
-       if (conf->end_chunk_given) {
+               pard->first_chunk = afh_get_start_chunk(afhi->chunks_total + bc,
+                       &pard->afhi, pard->audio_format_num);
+       if (lls_opt_given(r_e)) {
+               int32_t ec = lls_int32_val(0, r_e);
                ret = -ERRNO_TO_PARA_ERROR(EINVAL);
-               if (PARA_ABS(conf->end_chunk_arg) > afhi->chunks_total)
+               msg = "invalid end chunk";
+               if (PARA_ABS(ec) > afhi->chunks_total)
                        goto out_clear_afhi;
-               if (conf->end_chunk_arg >= 0)
-                       pard->last_chunk = conf->end_chunk_arg;
+               if (ec >= 0)
+                       pard->last_chunk = ec;
                else
-                       pard->last_chunk = afhi->chunks_total + conf->end_chunk_arg;
+                       pard->last_chunk = afhi->chunks_total + ec;
        } else
                pard->last_chunk = afhi->chunks_total - 1;
        ret = -ERRNO_TO_PARA_ERROR(EINVAL);
+       msg = "begin chunk >= end chunk!?";
        if (pard->first_chunk >= pard->last_chunk)
                goto out_clear_afhi;
        pard->current_chunk = pard->first_chunk;
        return pard->audio_format_num;
 out_clear_afhi:
        clear_afhi(afhi);
+       PARA_ERROR_LOG("%s: %s\n", fn, msg);
 out_unmap:
        para_munmap(pard->map, pard->map_size);
        close(pard->fd);
@@ -150,6 +138,7 @@ static void afh_recv_close(struct receiver_node *rn)
        clear_afhi(&pard->afhi);
        para_munmap(pard->map, pard->map_size);
        close(pard->fd);
+       afh_close(pard->afh_context, pard->audio_format_num);
        freep(&rn->private_data);
 }
 
@@ -158,13 +147,14 @@ static void afh_recv_pre_select(struct sched *s, void *context)
        struct receiver_node *rn = context;
        struct private_afh_recv_data *pard = rn->private_data;
        struct afh_info *afhi = &pard->afhi;
-       struct afh_recv_args_info *conf = rn->conf;
+       struct lls_parse_result *lpr = rn->lpr;
        struct timeval chunk_time;
        int state = generic_recv_pre_select(s, rn);
+       unsigned j_given = RECV_CMD_OPT_GIVEN(AFH, JUST_IN_TIME, lpr);
 
        if (state <= 0)
                return;
-       if (!conf->just_in_time_given) {
+       if (!j_given) {
                sched_min_delay(s);
                return;
        }
@@ -176,20 +166,22 @@ static void afh_recv_pre_select(struct sched *s, void *context)
 static int afh_recv_post_select(__a_unused struct sched *s, void *context)
 {
        struct receiver_node *rn = context;
-       struct afh_recv_args_info *conf = rn->conf;
+       struct lls_parse_result *lpr = rn->lpr;
        struct private_afh_recv_data *pard = rn->private_data;
        struct btr_node *btrn = rn->btrn;
        struct afh_info *afhi = &pard->afhi;
        int ret;
        char *buf;
-       const char *start, *end;
+       const char *start;
        size_t size;
        struct timeval chunk_time;
+       unsigned j_given = RECV_CMD_OPT_GIVEN(AFH, JUST_IN_TIME, lpr);
+       unsigned H_given = RECV_CMD_OPT_GIVEN(AFH, NO_HEADER, lpr);
 
        ret = btr_node_status(btrn, 0, BTR_NT_ROOT);
        if (ret <= 0)
                goto out;
-       if (pard->first_chunk > 0 && !conf->no_header_given) {
+       if (pard->first_chunk > 0 && !H_given) {
                char *header;
                afh_get_header(afhi, pard->audio_format_num, pard->map,
                        pard->map_size, &header, &size);
@@ -201,12 +193,17 @@ static int afh_recv_post_select(__a_unused struct sched *s, void *context)
                        afh_free_header(header, pard->audio_format_num);
                }
        }
-       if (!conf->just_in_time_given) {
-               afh_get_chunk(pard->first_chunk, afhi, pard->map, &start, &size);
-               afh_get_chunk(pard->last_chunk, afhi, pard->map, &end, &size);
-               end += size;
-               PARA_INFO_LOG("adding %td bytes\n", end - start);
-               btr_add_output_dont_free(start, end - start, btrn);
+       if (!j_given) {
+               long unsigned n;
+               for (n = pard->first_chunk; n < pard->last_chunk; n++) {
+                       ret = afh_get_chunk(n, afhi, pard->audio_format_num,
+                               pard->map, pard->map_size, &start, &size,
+                               &pard->afh_context);
+                       if (ret < 0)
+                               goto out;
+                       PARA_DEBUG_LOG("adding %zu bytes\n", size);
+                       btr_add_output_dont_free(start, size, btrn);
+               }
                ret = -E_RECV_EOF;
                goto out;
        }
@@ -219,7 +216,12 @@ static int afh_recv_post_select(__a_unused struct sched *s, void *context)
                if (ret > 0)
                        goto out;
        }
-       afh_get_chunk(pard->current_chunk, afhi, pard->map, &start, &size);
+       ret = afh_get_chunk(pard->current_chunk, afhi,
+               pard->audio_format_num, pard->map,
+               pard->map_size, &start, &size,
+               &pard->afh_context);
+       if (ret < 0)
+               goto out;
        PARA_DEBUG_LOG("adding chunk %u\n", pard->current_chunk);
        btr_add_output_dont_free(start, size, btrn);
        if (pard->current_chunk >= pard->last_chunk) {
@@ -236,26 +238,10 @@ out:
        return ret;
 }
 
-/**
- * The init function of the afh receiver.
- *
- * \param r Pointer to the receiver struct to initialize.
- *
- * This initializes all function pointers of \a r.
- */
-void afh_recv_init(struct receiver *r)
-{
-       struct afh_recv_args_info dummy;
-
-       afh_init();
-       afh_recv_cmdline_parser_init(&dummy);
-       r->open = afh_recv_open;
-       r->close = afh_recv_close;
-       r->pre_select = afh_recv_pre_select;
-       r->post_select = afh_recv_post_select;
-       r->parse_config = afh_recv_parse_config;
-       r->free_config = afh_recv_free_config;
-       r->execute = afh_execute;
-       r->help = (struct ggo_help)DEFINE_GGO_HELP(afh_recv);
-       afh_recv_cmdline_parser_free(&dummy);
-}
+const struct receiver lsg_recv_cmd_com_afh_user_data = {
+       .open = afh_recv_open,
+       .close = afh_recv_close,
+       .pre_select = afh_recv_pre_select,
+       .post_select = afh_recv_post_select,
+       .execute = afh_execute,
+};
diff --git a/afs.c b/afs.c
index 0946b6d..b6cce36 100644 (file)
--- a/afs.c
+++ b/afs.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file afs.c Paraslash's audio file selector. */
 
 #include <signal.h>
 #include <fnmatch.h>
 #include <osl.h>
+#include <lopsub.h>
 #include <arpa/inet.h>
 #include <sys/un.h>
 #include <netdb.h>
+#include <lopsub.h>
 
-#include "server.cmdline.h"
+#include "server.lsg.h"
+#include "server_cmd.lsg.h"
 #include "para.h"
 #include "error.h"
 #include "crypt.h"
 #include "string.h"
 #include "afh.h"
 #include "afs.h"
-#include "server.h"
 #include "net.h"
+#include "server.h"
 #include "ipc.h"
 #include "list.h"
 #include "sched.h"
 #include "sideband.h"
 #include "command.h"
 
-/** The osl tables used by afs. \sa blob.c. */
+/** The osl tables used by afs. \sa \ref blob.c. */
 enum afs_table_num {
-       /** Contains audio file information. See aft.c. */
+       /** Contains audio file information. See \ref aft.c. */
        TBLNUM_AUDIO_FILES,
-       /** The table for the paraslash attributes. See attribute.c. */
+       /** The table for the paraslash attributes. See \ref attribute.c. */
        TBLNUM_ATTRIBUTES,
        /**
         * Paraslash's scoring system is based on Gaussian normal
         * distributions, and the relevant data is stored in the rbtrees of an
-        * osl table containing only volatile columns.  See score.c for
+        * osl table containing only volatile columns. See \ref score.c for
         * details.
         */
        TBLNUM_SCORES,
        /**
         * A standard blob table containing the mood definitions. For details
-        * see mood.c.
+        * see \ref mood.c.
         */
        TBLNUM_MOODS,
        /** A blob table containing lyrics on a per-song basis. */
@@ -75,11 +74,6 @@ static struct afs_table afs_tables[NUM_AFS_TABLES] = {
 struct command_task {
        /** The file descriptor for the local socket. */
        int fd;
-       /**
-        * Value sent by the command handlers to identify themselves as
-        * children of the running para_server.
-        */
-       uint32_t cookie;
        /** The associated task structure. */
        struct task *task;
 };
@@ -94,15 +88,6 @@ static struct signal_task *signal_task;
 static enum play_mode current_play_mode;
 static char *current_mop; /* mode or playlist specifier. NULL means dummy mood */
 
-/**
- * A random number used to "authenticate" the connection.
- *
- * para_server picks this number by random before it forks the afs process. The
- * command handlers know this number as well and write it to the afs socket,
- * together with the id of the shared memory area which contains the payload of
- * the afs command. A local process has to know this number to abuse the afs
- * service provided by the local socket.
- */
 extern uint32_t afs_socket_cookie;
 
 /**
@@ -127,7 +112,7 @@ extern uint32_t afs_socket_cookie;
  * command socket, so that the handler process can read the id, attach the
  * shared memory area and use the result.
  *
- * \sa struct callback_result.
+ * \sa \ref struct callback_result.
  */
 struct callback_query {
        /** The function to be called. */
@@ -143,7 +128,7 @@ struct callback_query {
  * into the shared memory area holding the result, mainly to let the command
  * handler know the size of the result.
  *
- * \sa struct callback_query.
+ * \sa \ref struct callback_query.
  */
 struct callback_result {
        /** The number of bytes of the result. */
@@ -198,9 +183,8 @@ static int dispatch_result(int result_shmid, callback_result_handler *handler,
  * shmid are passed to that function as an osl object. The private_result_data
  * pointer is passed as the second argument to \a result_handler.
  *
- * \return Number of shared memory areas dispatched on success, negative on errors.
- *
- * \sa send_option_arg_callback_request(), send_standard_callback_request().
+ * \return Number of shared memory areas dispatched on success, negative on
+ * errors.
  */
 int send_callback_request(afs_callback *f, struct osl_object *query,
                callback_result_handler *result_handler,
@@ -235,7 +219,7 @@ int send_callback_request(afs_callback *f, struct osl_object *query,
        *(uint32_t *)buf = afs_socket_cookie;
        *(int *)(buf + sizeof(afs_socket_cookie)) = query_shmid;
 
-       ret = connect_local_socket(conf.afs_socket_arg);
+       ret = connect_local_socket(OPT_STRING_VAL(AFS_SOCKET));
        if (ret < 0)
                goto out;
        fd = ret;
@@ -280,73 +264,33 @@ out:
 }
 
 /**
- * Send a callback request passing an options structure and an argument vector.
+ * Wrapper for send_callback_request() which passes a lopsub parse result.
  *
- * \param options pointer to an arbitrary data structure.
- * \param argc Argument count.
- * \param argv Standard argument vector.
  * \param f The callback function.
- * \param result_handler See \ref send_callback_request.
- * \param private_result_data See \ref send_callback_request.
+ * \param cmd Needed for (de-)serialization.
+ * \param lpr Must match cmd.
+ * \param private_result_data Passed to send_callback_request().
  *
- * Some command handlers pass command-specific options to a callback, together
- * with a list of further arguments (often a list of audio files). This
- * function allows to pass an arbitrary structure (given as an osl object) and
- * a usual argument vector to the specified callback.
+ * This function serializes the parse result given by the lpr pointer into a
+ * buffer. The buffer is sent as the query to the afs process with the callback
+ * mechanism.
  *
- * \return The return value of the underlying call to \ref
- * send_callback_request().
- *
- * \sa send_standard_callback_request(), send_callback_request().
+ * \return The return value of the underlying call to send_callback_request().
  */
-int send_option_arg_callback_request(struct osl_object *options,
-               int argc,  char * const * const argv, afs_callback *f,
-               callback_result_handler *result_handler,
-               void *private_result_data)
+int send_lls_callback_request(afs_callback *f,
+               const struct lls_command * const cmd,
+               struct lls_parse_result *lpr, void *private_result_data)
 {
-       char *p;
-       int i, ret;
-       struct osl_object query = {.size = options? options->size : 0};
-
-       for (i = 0; i < argc; i++)
-               query.size += strlen(argv[i]) + 1;
-       query.data = para_malloc(query.size);
-       p = query.data;
-       if (options) {
-               memcpy(query.data, options->data, options->size);
-               p += options->size;
-       }
-       for (i = 0; i < argc; i++) {
-               strcpy(p, argv[i]); /* OK */
-               p += strlen(argv[i]) + 1;
-       }
-       ret = send_callback_request(f, &query, result_handler,
-               private_result_data);
-       free(query.data);
-       return ret;
-}
+       struct osl_object query;
+       char *buf = NULL;
+       int ret = lls_serialize_parse_result(lpr, cmd, &buf, &query.size);
 
-/**
- * Send a callback request with an argument vector only.
- *
- * \param argc The same meaning as in send_option_arg_callback_request().
- * \param argv The same meaning as in send_option_arg_callback_request().
- * \param f The same meaning as in send_option_arg_callback_request().
- * \param result_handler See \ref send_callback_request.
- * \param private_result_data See \ref send_callback_request.
- *
- * This is similar to send_option_arg_callback_request(), but no options buffer
- * is passed to the parent process.
- *
- * \return The return value of the underlying call to
- * send_option_arg_callback_request().
- */
-int send_standard_callback_request(int argc,  char * const * const argv,
-               afs_callback *f, callback_result_handler *result_handler,
-               void *private_result_data)
-{
-       return send_option_arg_callback_request(NULL, argc, argv, f, result_handler,
+       assert(ret >= 0);
+       query.data = buf;
+       ret = send_callback_request(f, &query, afs_cb_result_handler,
                private_result_data);
+       free(buf);
+       return ret;
 }
 
 static int action_if_pattern_matches(struct osl_row *row, void *data)
@@ -354,30 +298,37 @@ static int action_if_pattern_matches(struct osl_row *row, void *data)
        struct pattern_match_data *pmd = data;
        struct osl_object name_obj;
        const char *p, *name;
-       int ret = osl(osl_get_object(pmd->table, row, pmd->match_col_num, &name_obj));
-       const char *pattern_txt = (const char *)pmd->patterns.data;
+       int i, ret;
 
+       ret = osl(osl_get_object(pmd->table, row, pmd->match_col_num,
+               &name_obj));
        if (ret < 0)
                return ret;
        name = (char *)name_obj.data;
        if ((!name || !*name) && (pmd->pm_flags & PM_SKIP_EMPTY_NAME))
                return 1;
-       if (pmd->patterns.size == 0 &&
-                       (pmd->pm_flags & PM_NO_PATTERN_MATCHES_EVERYTHING)) {
-               pmd->num_matches++;
-               return pmd->action(pmd->table, row, name, pmd->data);
+       if (lls_num_inputs(pmd->lpr) == 0) {
+               if (pmd->pm_flags & PM_NO_PATTERN_MATCHES_EVERYTHING) {
+                       pmd->num_matches++;
+                       return pmd->action(pmd->table, row, name, pmd->data);
+               }
        }
-       for (p = pattern_txt; p < pattern_txt + pmd->patterns.size;
-                       p += strlen(p) + 1) {
+       i = pmd->input_skip;
+       for (;;) {
+               if (i >= lls_num_inputs(pmd->lpr))
+                       break;
+               p = lls_input(i, pmd->lpr);
                ret = fnmatch(p, name, pmd->fnmatch_flags);
-               if (ret == FNM_NOMATCH)
-                       continue;
-               if (ret)
-                       return -E_FNMATCH;
-               ret = pmd->action(pmd->table, row, name, pmd->data);
-               if (ret >= 0)
-                       pmd->num_matches++;
-               return ret;
+               if (ret != FNM_NOMATCH) {
+                       if (ret != 0)
+                               return -E_FNMATCH;
+                       ret = pmd->action(pmd->table, row, name, pmd->data);
+                       if (ret >= 0)
+                               pmd->num_matches++;
+                       return ret;
+
+               }
+               i++;
        }
        return 1;
 }
@@ -411,12 +362,12 @@ int for_each_matching_row(struct pattern_match_data *pmd)
  * \a obj1 is found, respectively, to be less than, to match, or be greater than
  * obj2.
  *
- * \sa strcmp(3), strncmp(3), osl_compare_func.
+ * \sa strcmp(3), strncmp(3).
  */
 int string_compare(const struct osl_object *obj1, const struct osl_object *obj2)
 {
-       const char *str1 = (const char *)obj1->data;
-       const char *str2 = (const char *)obj2->data;
+       const char *str1 = obj1->data;
+       const char *str2 = obj2->data;
        return strncmp(str1, str2, PARA_MIN(obj1->size, obj2->size));
 }
 
@@ -463,17 +414,17 @@ static int pass_afd(int fd, char *buf, size_t size)
  *
  * \return Standard.
  *
- * \sa open_and_update_audio_file().
+ * \sa \ref open_and_update_audio_file().
  */
 static int open_next_audio_file(void)
 {
-       struct audio_file_data afd;
-       int ret, shmid;
+       int ret, shmid, fd;
        char buf[8];
 
-       ret = open_and_update_audio_file(&afd);
+       ret = open_and_update_audio_file(&fd);
        if (ret < 0) {
-               PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+               if (ret != -OSL_ERRNO_TO_PARA_ERROR(E_OSL_RB_KEY_NOT_FOUND))
+                       PARA_ERROR_LOG("%s\n", para_strerror(-ret));
                goto no_admissible_files;
        }
        shmid = ret;
@@ -483,8 +434,8 @@ static int open_next_audio_file(void)
        }
        *(uint32_t *)buf = NEXT_AUDIO_FILE;
        *(uint32_t *)(buf + 4) = (uint32_t)shmid;
-       ret = pass_afd(afd.fd, buf, 8);
-       close(afd.fd);
+       ret = pass_afd(fd, buf, 8);
+       close(fd);
        if (ret >= 0)
                return ret;
 destroy:
@@ -497,23 +448,30 @@ no_admissible_files:
 }
 
 /* Never fails if arg == NULL */
-static int activate_mood_or_playlist(const char *arg, int *num_admissible)
+static int activate_mood_or_playlist(const char *arg, int *num_admissible,
+               char **errmsg)
 {
        enum play_mode mode;
        int ret;
 
        if (!arg) {
-               ret = change_current_mood(NULL); /* always successful */
+               ret = change_current_mood(NULL, NULL); /* always successful */
                mode = PLAY_MODE_MOOD;
        } else {
                if (!strncmp(arg, "p/", 2)) {
                        ret = playlist_open(arg + 2);
+                       if (ret < 0 && errmsg)
+                               *errmsg = make_message( "could not open %s",
+                                       arg);
                        mode = PLAY_MODE_PLAYLIST;
                } else if (!strncmp(arg, "m/", 2)) {
-                       ret = change_current_mood(arg + 2);
+                       ret = change_current_mood(arg + 2, errmsg);
                        mode = PLAY_MODE_MOOD;
-               } else
-                       return -E_AFS_SYNTAX;
+               } else {
+                       if (errmsg)
+                               *errmsg = make_message("%s: parse error", arg);
+                       return -ERRNO_TO_PARA_ERROR(EINVAL);
+               }
                if (ret < 0)
                        return ret;
        }
@@ -528,10 +486,12 @@ static int activate_mood_or_playlist(const char *arg, int *num_admissible)
                        strncpy(mmd->afs_mode_string, arg,
                                sizeof(mmd->afs_mode_string));
                        mmd->afs_mode_string[sizeof(mmd->afs_mode_string) - 1] = '\0';
+                       mmd->events++;
                        mutex_unlock(mmd_mutex);
                } else {
                        mutex_lock(mmd_mutex);
                        strcpy(mmd->afs_mode_string, "dummy");
+                       mmd->events++;
                        mutex_unlock(mmd_mutex);
                        current_mop = NULL;
                }
@@ -590,74 +550,88 @@ static void flush_and_free_pb(struct para_buffer *pb)
 
 static int com_select_callback(struct afs_callback_arg *aca)
 {
-       const char *arg = aca->query.data;
+       const struct lls_command *cmd = SERVER_CMD_CMD_PTR(SELECT);
+       const char *arg;
        int num_admissible, ret;
+       char *errmsg;
 
+       ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr);
+       assert(ret >= 0);
+       arg = lls_input(0, aca->lpr);
        ret = clear_score_table();
        if (ret < 0) {
-               para_printf(&aca->pbout, "could not clear score table: %s\n",
-                       para_strerror(-ret));
-               return ret;
+               para_printf(&aca->pbout, "could not clear score table\n");
+               goto free_lpr;
        }
        if (current_play_mode == PLAY_MODE_MOOD)
                close_current_mood();
        else
                playlist_close();
-       ret = activate_mood_or_playlist(arg, &num_admissible);
+       ret = activate_mood_or_playlist(arg, &num_admissible, &errmsg);
        if (ret >= 0)
                goto out;
        /* ignore subsequent errors (but log them) */
+       para_printf(&aca->pbout, "%s\n", errmsg);
+       free(errmsg);
        para_printf(&aca->pbout, "could not activate %s\n", arg);
-       if (current_mop) {
+       if (current_mop && strcmp(current_mop, arg) != 0) {
                int ret2;
                para_printf(&aca->pbout, "switching back to %s\n", current_mop);
-               ret2 = activate_mood_or_playlist(current_mop, &num_admissible);
+               ret2 = activate_mood_or_playlist(current_mop, &num_admissible,
+                       &errmsg);
                if (ret2 >= 0)
                        goto out;
+               para_printf(&aca->pbout, "%s\n", errmsg);
+               free(errmsg);
                para_printf(&aca->pbout, "could not reactivate %s: %s\n",
                        current_mop, para_strerror(-ret2));
        }
        para_printf(&aca->pbout, "activating dummy mood\n");
-       activate_mood_or_playlist(NULL, &num_admissible);
+       activate_mood_or_playlist(NULL, &num_admissible, NULL);
 out:
-       para_printf(&aca->pbout, "activated %s (%d admissible files)\n",
-               current_mop? current_mop : "dummy mood", num_admissible);
+       para_printf(&aca->pbout, "activated %s (%d admissible file%s)\n",
+               current_mop? current_mop : "dummy mood", num_admissible,
+                       num_admissible == 1? "" : "s");
+free_lpr:
+       lls_free_parse_result(aca->lpr, cmd);
        return ret;
 }
 
-int com_select(struct command_context *cc)
+static int com_select(struct command_context *cc, struct lls_parse_result *lpr)
 {
-       struct osl_object query;
+       const struct lls_command *cmd = SERVER_CMD_CMD_PTR(SELECT);
+       char *errctx;
+       int ret = lls(lls_check_arg_count(lpr, 1, 1, &errctx));
 
-       if (cc->argc != 2)
-               return -E_AFS_SYNTAX;
-       query.data = cc->argv[1];
-       query.size = strlen(cc->argv[1]) + 1;
-       return send_callback_request(com_select_callback, &query,
-               &afs_cb_result_handler, cc);
+       if (ret < 0) {
+               send_errctx(cc, errctx);
+               return ret;
+       }
+       return send_lls_callback_request(com_select_callback, cmd, lpr, cc);
 }
+EXPORT_SERVER_CMD_HANDLER(select);
 
-static void init_admissible_files(char *arg)
+static void init_admissible_files(const char *arg)
 {
-       if (activate_mood_or_playlist(arg, NULL) < 0)
-               activate_mood_or_playlist(NULL, NULL); /* always successful */
+       int ret = activate_mood_or_playlist(arg, NULL, NULL);
+       if (ret < 0) {
+               assert(arg);
+               PARA_WARNING_LOG("could not activate %s: %s\n", arg,
+                       para_strerror(-ret));
+               activate_mood_or_playlist(NULL, NULL, NULL);
+       }
 }
 
 static int setup_command_socket_or_die(void)
 {
        int ret, socket_fd;
-       char *socket_name = conf.afs_socket_arg;
+       const char *socket_name = OPT_STRING_VAL(AFS_SOCKET);
 
        unlink(socket_name);
-       ret = create_local_socket(socket_name, 0);
+       ret = create_local_socket(socket_name);
        if (ret < 0) {
-               ret = create_local_socket(socket_name,
-                       S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IROTH);
-               if (ret < 0) {
-                       PARA_EMERG_LOG("%s: %s\n", para_strerror(-ret),
-                               socket_name);
-                       exit(EXIT_FAILURE);
-               }
+               PARA_EMERG_LOG("%s: %s\n", para_strerror(-ret), socket_name);
+               exit(EXIT_FAILURE);
        }
        socket_fd = ret;
        PARA_INFO_LOG("listening on socket %s (fd %d)\n", socket_name,
@@ -665,21 +639,23 @@ static int setup_command_socket_or_die(void)
        return socket_fd;
 }
 
+static char *database_dir;
+
 static void close_afs_tables(void)
 {
        int i;
        PARA_NOTICE_LOG("closing afs_tables\n");
        for (i = 0; i < NUM_AFS_TABLES; i++)
                afs_tables[i].close();
+       free(database_dir);
+       database_dir = NULL;
 }
 
-static char *database_dir;
-
 static void get_database_dir(void)
 {
        if (!database_dir) {
-               if (conf.afs_database_dir_given)
-                       database_dir = para_strdup(conf.afs_database_dir_arg);
+               if (OPT_GIVEN(AFS_DATABASE_DIR))
+                       database_dir = para_strdup(OPT_STRING_VAL(AFS_DATABASE_DIR));
                else {
                        char *home = para_homedir();
                        database_dir = make_message(
@@ -712,8 +688,7 @@ static int open_afs_tables(void)
                ret = afs_tables[i].open(database_dir);
                if (ret >= 0)
                        continue;
-               PARA_ERROR_LOG("%s init: %s\n", afs_tables[i].name,
-                       para_strerror(-ret));
+               PARA_ERROR_LOG("could not open %s\n", afs_tables[i].name);
                break;
        }
        if (ret >= 0)
@@ -888,12 +863,12 @@ static int execute_server_command(fd_set *rfds)
                return ret;
        buf[n] = '\0';
        if (strcmp(buf, "new"))
-               return -E_BAD_CMD;
+               return -ERRNO_TO_PARA_ERROR(EINVAL);
        return open_next_audio_file();
 }
 
 /* returns 0 if no data available, 1 else */
-static int execute_afs_command(int fd, fd_set *rfds, uint32_t expected_cookie)
+static int execute_afs_command(int fd, fd_set *rfds)
 {
        uint32_t cookie;
        int query_shmid;
@@ -911,9 +886,9 @@ static int execute_afs_command(int fd, fd_set *rfds, uint32_t expected_cookie)
                return 1;
        }
        cookie = *(uint32_t *)buf;
-       if (cookie != expected_cookie) {
+       if (cookie != afs_socket_cookie) {
                PARA_NOTICE_LOG("received invalid cookie (got %u, expected %u)\n",
-                       (unsigned)cookie, (unsigned)expected_cookie);
+                       (unsigned)cookie, (unsigned)afs_socket_cookie);
                return 1;
        }
        query_shmid = *(int *)(buf + sizeof(cookie));
@@ -951,7 +926,7 @@ static int command_post_select(struct sched *s, void *context)
        }
        /* Check the list of connected clients. */
        list_for_each_entry_safe(client, tmp, &afs_client_list, node) {
-               ret = execute_afs_command(client->fd, &s->rfds, ct->cookie);
+               ret = execute_afs_command(client->fd, &s->rfds);
                if (ret == 0) { /* prevent bogus connection flooding */
                        struct timeval diff;
                        tv_diff(now, &client->connect_time, &diff);
@@ -982,11 +957,10 @@ static int command_post_select(struct sched *s, void *context)
        return 0;
 }
 
-static void register_command_task(uint32_t cookie, struct sched *s)
+static void register_command_task(struct sched *s)
 {
        struct command_task *ct = &command_task_struct;
        ct->fd = setup_command_socket_or_die();
-       ct->cookie = cookie;
 
        ct->task = task_register(&(struct task_info) {
                .name = "afs command",
@@ -999,10 +973,9 @@ static void register_command_task(uint32_t cookie, struct sched *s)
 /**
  * Initialize the audio file selector process.
  *
- * \param cookie The value used for "authentication".
  * \param socket_fd File descriptor used for communication with the server.
  */
-__noreturn void afs_init(uint32_t cookie, int socket_fd)
+__noreturn void afs_init(int socket_fd)
 {
        static struct sched s;
        int i, ret;
@@ -1018,10 +991,9 @@ __noreturn void afs_init(uint32_t cookie, int socket_fd)
        ret = mark_fd_nonblocking(server_socket);
        if (ret < 0)
                goto out_close;
-       PARA_INFO_LOG("server_socket: %d, afs_socket_cookie: %u\n",
-               server_socket, (unsigned) cookie);
-       init_admissible_files(conf.afs_initial_mode_arg);
-       register_command_task(cookie, &s);
+       PARA_INFO_LOG("server_socket: %d\n", server_socket);
+       init_admissible_files(OPT_STRING_VAL(AFS_INITIAL_MODE));
+       register_command_task(&s);
        s.default_timeout.tv_sec = 0;
        s.default_timeout.tv_usec = 999 * 1000;
        ret = write(socket_fd, "\0", 1);
@@ -1033,9 +1005,14 @@ __noreturn void afs_init(uint32_t cookie, int socket_fd)
        }
        ret = schedule(&s);
        sched_shutdown(&s);
+       close_current_mood();
 out_close:
        close_afs_tables();
 out:
+       signal_shutdown(signal_task);
+       free_status_items();
+       free(current_mop);
+       free_lpr();
        if (ret < 0)
                PARA_EMERG_LOG("%s\n", para_strerror(-ret));
        exit(EXIT_FAILURE);
@@ -1047,6 +1024,7 @@ static int com_init_callback(struct afs_callback_arg *aca)
        int i, ret;
 
        close_afs_tables();
+       get_database_dir();
        for (i = 0; i < NUM_AFS_TABLES; i++) {
                struct afs_table *t = &afs_tables[i];
 
@@ -1065,28 +1043,30 @@ static int com_init_callback(struct afs_callback_arg *aca)
        }
        ret = open_afs_tables();
        if (ret < 0)
-               para_printf(&aca->pbout, "cannot open afs tables\n");
+               para_printf(&aca->pbout, "cannot open afs tables: %s\n",
+                       para_strerror(-ret));
 out:
        return ret;
 }
 
-int com_init(struct command_context *cc)
+static int com_init(struct command_context *cc, struct lls_parse_result *lpr)
 {
        int i, j, ret;
        uint32_t table_mask = (1 << (NUM_AFS_TABLES + 1)) - 1;
        struct osl_object query = {.data = &table_mask,
                .size = sizeof(table_mask)};
+       unsigned num_inputs = lls_num_inputs(lpr);
 
        ret = make_database_dir();
        if (ret < 0)
                return ret;
-       if (cc->argc != 1) {
+       if (num_inputs > 0) {
                table_mask = 0;
-               for (i = 1; i < cc->argc; i++) {
+               for (i = 0; i < num_inputs; i++) {
                        for (j = 0; j < NUM_AFS_TABLES; j++) {
                                struct afs_table *t = &afs_tables[j];
 
-                               if (strcmp(cc->argv[i], t->name))
+                               if (strcmp(lls_input(i, lpr), t->name))
                                        continue;
                                table_mask |= (1 << j);
                                break;
@@ -1098,77 +1078,37 @@ int com_init(struct command_context *cc)
        return send_callback_request(com_init_callback, &query,
                afs_cb_result_handler, cc);
 }
+EXPORT_SERVER_CMD_HANDLER(init);
 
-/**
- * Flags for the check command.
- *
- * \sa com_check().
- */
-enum com_check_flags {
-       /** Check the audio file table. */
-       CHECK_AFT = 1,
-       /** Check the mood table. */
-       CHECK_MOODS = 2,
-       /** Check the playlist table. */
-       CHECK_PLAYLISTS = 4,
-       /** Check the attribute table against the audio file table. */
-       CHECK_ATTS = 8
-};
-
-int com_check(struct command_context *cc)
+static int com_check(struct command_context *cc, struct lls_parse_result *lpr)
 {
-       unsigned flags = 0;
-       int i, ret;
+       const struct lls_opt_result *r_a = SERVER_CMD_OPT_RESULT(CHECK, AFT, lpr);
+       const struct lls_opt_result *r_A = SERVER_CMD_OPT_RESULT(CHECK, ATTRIBUTE, lpr);
+       const struct lls_opt_result *r_m = SERVER_CMD_OPT_RESULT(CHECK, MOOD, lpr);
+       const struct lls_opt_result *r_p = SERVER_CMD_OPT_RESULT(CHECK, PLAYLIST, lpr);
+       bool noopt = !lls_opt_given(r_a) && !lls_opt_given(r_A)
+               && !lls_opt_given(r_m) && !lls_opt_given(r_p);
+       int ret;
 
-       for (i = 1; i < cc->argc; i++) {
-               const char *arg = cc->argv[i];
-               if (arg[0] != '-')
-                       break;
-               if (!strcmp(arg, "--")) {
-                       i++;
-                       break;
-               }
-               if (!strcmp(arg, "-a")) {
-                       flags |= CHECK_AFT;
-                       continue;
-               }
-               if (!strcmp(arg, "-A")) {
-                       flags |= CHECK_ATTS;
-                       continue;
-               }
-               if (!strcmp(arg, "-p")) {
-                       flags |= CHECK_PLAYLISTS;
-                       continue;
-               }
-               if (!strcmp(arg, "-m")) {
-                       flags |= CHECK_MOODS;
-                       continue;
-               }
-               return -E_AFS_SYNTAX;
-       }
-       if (i < cc->argc)
-               return -E_AFS_SYNTAX;
-       if (!flags)
-               flags = ~0U;
-       if (flags & CHECK_AFT) {
+       if (noopt || lls_opt_given(r_a)) {
                ret = send_callback_request(aft_check_callback, NULL,
                        afs_cb_result_handler, cc);
                if (ret < 0)
                        return ret;
        }
-       if (flags & CHECK_ATTS) {
+       if (noopt || lls_opt_given(r_A)) {
                ret = send_callback_request(attribute_check_callback, NULL,
                        afs_cb_result_handler, cc);
                if (ret < 0)
                        return ret;
        }
-       if (flags & CHECK_PLAYLISTS) {
+       if (noopt || lls_opt_given(r_p)) {
                ret = send_callback_request(playlist_check_callback,
                        NULL, afs_cb_result_handler, cc);
                if (ret < 0)
                        return ret;
        }
-       if (flags & CHECK_MOODS) {
+       if (noopt || lls_opt_given(r_m)) {
                ret = send_callback_request(mood_check_callback, NULL,
                        afs_cb_result_handler, cc);
                if (ret < 0)
@@ -1176,6 +1116,7 @@ int com_check(struct command_context *cc)
        }
        return 1;
 }
+EXPORT_SERVER_CMD_HANDLER(check);
 
 /**
  * The afs event dispatcher.
diff --git a/afs.cmd b/afs.cmd
deleted file mode 100644 (file)
index 76b5f4d..0000000
--- a/afs.cmd
+++ /dev/null
@@ -1,304 +0,0 @@
-BN: afs
-SF: afs.c aft.c attribute.c
-SN: list of afs commands
-TM: mood lyr img pl
----
-N: add
-P: AFS_READ | AFS_WRITE
-D: Add or update audio files.
-U: add [-a] [-l] [-f] [-v] path...
-H: Each path must be absolute and refer to either an audio file, or a
-H: directory. In case of a directory, all audio files in that directory
-H: are added recursively. Only absolute paths are accepted.
-H:
-H: Options:
-H:
-H: -a  Add all files. The default is to add only files ending in a
-H:     known suffix for a supported audio format.
-H:
-H: -l  Add files lazily. If the path already exists in the
-H:     database, skip this file.  This operation is really cheap. Useful
-H:     to update large directories after some files have been added or
-H:     deleted.
-H:
-H: -f  Force adding/updating. Recompute the audio format handler data
-H:     even if a file with the same path and the same hash value exists.
-H:
-H: -v  Verbose mode. Print what is being done.
----
-N: init
-P: AFS_READ | AFS_WRITE
-D: Initialize the osl tables for the audio file selector.
-U: init [table_name ...]
-H: When invoked without arguments, this command creates all tables. Otherwise
-H: only the tables given by table_name... are created.
----
-N: ls
-P: AFS_READ
-D: List audio files.
-U: ls [-l=mode] [-p] [-a] [-r] [-d] [-s=order] [pattern...]
-H: Print a list of all audio files matching pattern.
-H:
-H: Options:
-H:
-H: -l=mode     Change listing mode. Defaults to short listing if not given.
-H:
-H:    Available modes:
-H:    s: short listing mode
-H:    l: long listing mode (equivalent to -l)
-H:    v: verbose listing mode
-H:    p: parser-friendly mode
-H:    m: mbox listing mode
-H:    c: chunk-table listing mode
-H:
-H: -F  List full paths. If this option is not specified, only the basename
-H:     of each file is printed.
-H: -p  Synonym for -F. Deprecated.
-H:
-H: -b  Print only the basename of each matching file. This is the default, so
-H:     the option is currently a no-op. It is recommended to specify this option,
-H:     though, as the default might change in a future release.
-H:
-H: -a  List only files that are admissible with respect to the current mood or
-H:     playlist.
-H:
-H: -r  Reverse sort order.
-H:
-H: -d  Print dates as seconds after the epoch.
-H:
-H: -s=order
-H:     Change sort order. Defaults to alphabetical path sort if not given.
-H:
-H:   Possible values for order:
-H:   p: by path
-H:   l: by last played time
-H:   s: by score (implies -a)
-H:   n: by num played count
-H:   f: by frequency
-H:   c: by number of channels
-H:   i: by image id
-H:   y: by lyrics id
-H:   b: by bit rate
-H:   d: by duration
-H:   a: by audio format
----
-N: lsatt
-P: AFS_READ
-D: List attributes.
-U: lsatt [-i] [-l] [-r] [pattern]
-H: Print the list of all defined attributes which match the given
-H: pattern. If no pattern is given, the full list is printed.
-H:
-H: Options:
-H:
-H: -i  Sort attributes by id. The default is to sort alphabetically by name.
-H:
-H: -l  Print a long listing containing both identifier and attribute name. The
-H:     default is to print only the name.
-H:
-H: -r  Reverse sort order.
----
-N: setatt
-P: AFS_READ | AFS_WRITE
-D: Set attribute(s) for all files matching a pattern.
-U: setatt attribute{+|-}... pattern
-H: Set ('+') or unset ('-') the given attributes for all audio files matching
-H: pattern.  Example:
-H:
-H:         setatt rock+ punk+ pop- '*foo.mp3'
-H:
-H: sets the 'rock' and the 'punk' attribute and unsets the 'pop'
-H: attribute of all files ending with 'foo.mp3'.
----
-N: addatt
-P: AFS_READ | AFS_WRITE
-D: Add new attribute(s).
-U: addatt attribute1...
-H: This adds new attributes to the attribute table. At most 64
-H: attributes may be defined.
----
-N: mvatt
-P: AFS_READ | AFS_WRITE
-D: Rename an attribute.
-U: mvatt old new
-H: Rename attribute old to new.
----
-N: check
-P: AFS_READ
-D: Run integrity checks against osl tables.
-U: check [-a] [-A] [-m] [-p]
-H: Check the audio file table, the attribute table, the mood definitions
-H: and all defined playlists. Report any inconsistencies.
-H:
-H: Options:
-H:
-H: -a  Run audio file table checks. Checks for entries in the audio file
-H:     table which are not present in the file system. Moreover, it checks
-H:     whether the lyrics id and all entries in the audio file table are
-H:     valid.
-H:
-H: -A  Check the attribute table against the afs attribute bitmask of
-H:     each audio file in the audio file table. Reports audio files
-H:     whose attribute bitmask is invalid, i.e., has a bit set which
-H:     does not correspond to any attribute of the attribute table.
-H:
-H: -m  Run syntax checks on all defined moods in the mood table.
-H:
-H: -p  Check all playlists for lines that correspond to files not contained
-H:     in the audio file table.
-H:
-H: If called without arguments, all checks are run.
----
-N: rmatt
-P: AFS_READ | AFS_WRITE
-D: Remove attribute(s).
-U: rmatt pattern...
-H: Remove all attributes matching any given pattern. All information
-H: about this attribute in the audio file table is lost.
----
-N: rm
-P: AFS_READ | AFS_WRITE
-D: Remove entries from the audio file table.
-U: rm [-v] [-f] [-p] pattern...
-H: Delete all entries in the audio file table that match any given pattern.  Note
-H: that this affects the table entries only; the command won't touch your audio
-H: files on disk.
-H:
-H: Options:
-H:
-H: -v  Verbose mode. Explain what is being done.
-H:
-H: -f  Force mode. Ignore nonexistent files. Don't complain if nothing
-H:     was removed.
-H:
-H: -p  Pathname match. Match a slash in the path only with a slash
-H:     in pattern and not by an asterisk (*) or a question mark
-H:     (?) metacharacter, nor by a bracket expression ([]) containing
-H:     a slash (see fnmatch(3)).
----
-N: touch
-P: AFS_READ | AFS_WRITE
-D: Manipulate the afs entry of audio files.
-U: touch [-n=numplayed] [-l=lastplayed] [-y=lyrics_id] [-i=image_id] [-a=amp] [-v] [-p] pattern
-H: If no option is given, the lastplayed field is set to the current time
-H: and the value of the numplayed field is increased by one. Otherwise,
-H: only the given options are taken into account.
-H:
-H: Options:
-H:
-H: -n  Set the numplayed count, i.e. the number of times this audio
-H:     file was selected for streaming so far.
-H:
-H: -l  Set the lastplayed time, i.e. the last time this audio file was
-H:     selected for streaming. The argument must be a number of seconds
-H:     since the epoch. Example:
-H:
-H:             touch -l=$(date +%s) file
-H:
-H:     sets the lastplayed time of 'file' to the current time.
-H:
-H: -y  Set the lyrics ID which specifies the lyrics data file associated
-H:     with the audio file.
-H:
-H: -i  Like -y, but sets the image ID.
-H:
-H: -a  Set the amplification value (0-255). This determines a scaling
-H:     factor by which the amplitude should be multiplied in order to
-H:     normalize the volume of the audio file.  A value of zero means
-H:     no amplification, 64 means the amplitude should be multiplied
-H:     by a factor of two, 128 by three and so on.
-H:
-H:     This value is used by the amp filter.
-H:
-H: -v  Verbose mode. Explain what is being done.
-H:
-H: -p  Pathname match. Match a slash in the path only with a slash
-H:     in pattern and not by an asterisk (*) or a question mark
-H:     (?) metacharacter, nor by a bracket expression ([]) containing
-H:     a slash (see fnmatch(3)).
----
-N: cpsi
-P: AFS_READ | AFS_WRITE
-D: Copy audio file selector info.
-U: cpsi [-a] [-y] [-i] [-l] [-n] [-v] source pattern...
-H: If no option, or only the -v option is given, all fields of the
-H: audio file selector info are copied to all files matching pattern.
-H: Otherwise, only the given options are taken into account.
-H:
-H: Options:
-H:
-H: -a  Copy attributes.
-H:
-H: -y  Copy the lyrics id.
-H:
-H: -i  Copy the image id.
-H:
-H: -l  Copy the lastplayed time.
-H:
-H: -n  Copy the numplayed count.
-H:
-H: -v  Verbose mode.
----
-N: select
-P: AFS_READ | AFS_WRITE
-D: Activate a mood or a playlist.
-U: select specifier/name
-H: The specifier is either 'm' or 'p' to indicate whether a playlist or
-H: a mood should be activated. Example:
-H:
-H:     select m/foo
-H:
-H: loads the mood named 'foo'.
----
-T: add
-N: add@member@
-O: int com_add@member@(struct command_context *cc);
-P: AFS_READ | AFS_WRITE
-D: Add stdin as a blob to the @member@ table.
-U: add@member@ @member@_name
-H: Read from stdin and ask the audio file selector to create a blob in the
-H: corresponding osl table. If the named blob already exists, it gets replaced
-H: with the new data.
----
-T: cat
-N: cat@member@
-O: int com_cat@member@(struct command_context *cc);
-P: AFS_READ
-D: Dump the contents of a blob of type @member@ to stdout.
-U: cat@member@ @member@_name
-H: Retrieve the named blob and write it to stdout.
----
-T: ls
-N: ls@member@
-O: int com_ls@member@(struct command_context *cc);
-P: AFS_READ
-D: List blobs of type @member@ which match a pattern.
-U: ls@member@ [-i] [-l] [-r] [pattern]
-H: Print the list of all blobs which match the given pattern. If no
-H: pattern is given, the full list is printed.
-H:
-H: Options:
-H:
-H: -i  Sort by identifier. The default is to sort alphabetically by name.
-H:
-H: -l  Print identifier and name. The default is to print only the name.
-H:
-H: -r  Reverse sort order.
----
-T: rm
-N: rm@member@
-O: int com_rm@member@(struct command_context *cc);
-P: AFS_READ | AFS_WRITE
-D: Remove blob(s) of type @member@ from the @member@ table.
-U: rm@member@ pattern...
-H: Remove all blobs whose name matches any of the given patterns.
----
-T: mv
-N: mv@member@
-O: int com_mv@member@(struct command_context *cc);
-P: AFS_READ | AFS_WRITE
-D: Rename a blob of type @member@.
-U: mv@member@ source_@member@_name dest_@member@_name
-H: Rename the blob identified by the source blob name to the destination blob
-H: name. The command fails if the source does not exist, or if the destination
-H: already exists.
diff --git a/afs.h b/afs.h
index 2f9d7e5..b160649 100644 (file)
--- a/afs.h
+++ b/afs.h
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file afs.h Exported symbols of the audio file selector. */
 
@@ -141,12 +137,14 @@ struct pattern_match_data {
        unsigned loop_col_num;
        /** Data from this column is matched against the given patterns. */
        unsigned match_col_num;
-       /** \see pattern_match_flags. */
+       /** \see \ref pattern_match_flags. */
        unsigned pm_flags;
        /** This value is passed verbatim to fnmatch(). */
        int fnmatch_flags;
-       /** Null-terminated array of patterns. */
-       struct osl_object patterns;
+       /** Obtained by deserializing the query buffer in the callback. */
+       struct lls_parse_result *lpr;
+       /** Do not try to match the first inputs of lpr */
+       unsigned input_skip;
        /** Data pointer passed to the action function. */
        void *data;
        /** Gets increased by one for each match. */
@@ -163,6 +161,15 @@ struct afs_callback_arg {
        struct osl_object query;
        /** Will be written on band SBD_OUTPUT, fully buffered. */
        struct para_buffer pbout;
+       /**
+        * Convenience pointer for the deserialized parse result.
+        *
+        * Most afs command handlers call \ref send_lls_callback_request() to
+        * serialize the parse result of the subcommand and pass it to the
+        * callback. In afs context a pointer to the deserialized parse result
+        * is stored here.
+        */
+       struct lls_parse_result *lpr;
 };
 
 /**
@@ -171,16 +178,14 @@ struct afs_callback_arg {
  * Therefore afs commands typically consist of two functions: The command
  * handler and the corresponding callback function that runs in afs context.
  *
- * \sa send_callback_request().
+ * \sa \ref send_callback_request().
  */
 typedef int afs_callback(struct afs_callback_arg *aca);
 
 /**
- * Callbacks send chunks to data back to the command handler. Pointers to
- * this type of function are used by \ref send_callback_request and friends
- * to deal with the data in the command handler process.
- *
- * \sa \ref send_callback_request().
+ * Some AFS callbacks need to send data back to the command handler. Pointers
+ * to this type of function are passed to \ref send_callback_request() and
+ * related functions to dispatch the data in the command handler process.
  */
 typedef int callback_result_handler(struct osl_object *result, uint8_t band, void *private);
 int afs_cb_result_handler(struct osl_object *result, uint8_t band, void *private);
@@ -215,19 +220,15 @@ _static_inline_ int afs_max_size_handler(char *buf, size_t size, void *private)
        return pass_buffer_as_shm(amshd->fd, amshd->band, buf, size);
 }
 
-__noreturn void afs_init(uint32_t cookie, int socket_fd);
+__noreturn void afs_init(int socket_fd);
 __must_check int afs_event(enum afs_events event, struct para_buffer *pb,
        void *data);
 int send_callback_request(afs_callback *f, struct osl_object *query,
                callback_result_handler *result_handler,
                void *private_result_data);
-int send_option_arg_callback_request(struct osl_object *options,
-               int argc,  char * const * const argv, afs_callback *f,
-               callback_result_handler *result_handler,
-               void *private_result_data);
-int send_standard_callback_request(int argc,  char * const * const argv,
-               afs_callback *f, callback_result_handler *result_handler,
-               void *private_result_data);
+int send_lls_callback_request(afs_callback *f,
+               const struct lls_command * const cmd,
+               struct lls_parse_result *lpr, void *private_result_data);
 int string_compare(const struct osl_object *obj1, const struct osl_object *obj2);
 int for_each_matching_row(struct pattern_match_data *pmd);
 
@@ -254,13 +255,14 @@ int attribute_check_callback(struct afs_callback_arg *aca);
 void aft_init(struct afs_table *t);
 int aft_get_row_of_path(const char *path, struct osl_row **row);
 int aft_check_attributes(uint64_t att_mask, struct para_buffer *pb);
-int open_and_update_audio_file(struct audio_file_data *afd);
+int open_and_update_audio_file(int *fd);
 int load_afd(int shmid, struct audio_file_data *afd);
 int get_afsi_of_row(const struct osl_row *row, struct afs_info *afsi);
 int get_afhi_of_row(const struct osl_row *row, struct afh_info *afhi);
 int get_audio_file_path_of_row(const struct osl_row *row, char **path);
 int audio_file_loop(void *private_data, osl_rbtree_loop_func *func);
 int aft_check_callback(struct afs_callback_arg *aca);
+void free_status_items(void);
 
 /* playlist */
 int playlist_open(const char *name);
@@ -283,10 +285,12 @@ int playlist_check_callback(struct afs_callback_arg *aca);
                struct para_buffer *pb, void *data); \
        extern struct osl_table *table_name ## _table;
 
+/** \cond blob_symbols */
 DECLARE_BLOB_SYMBOLS(lyrics, lyr);
 DECLARE_BLOB_SYMBOLS(images, img);
 DECLARE_BLOB_SYMBOLS(moods, mood);
 DECLARE_BLOB_SYMBOLS(playlists, pl);
+/** \endcond blob_symbols */
 
 /** The columns of an abstract blob table. */
 enum blob_table_columns {
diff --git a/aft.c b/aft.c
index 1afc16b..c8c98e7 100644 (file)
--- a/aft.c
+++ b/aft.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file aft.c Audio file table functions. */
 
@@ -11,7 +7,9 @@
 #include <fnmatch.h>
 #include <sys/shm.h>
 #include <osl.h>
+#include <lopsub.h>
 
+#include "server_cmd.lsg.h"
 #include "para.h"
 #include "error.h"
 #include "crypt.h"
 #include "sideband.h"
 #include "command.h"
 
-static struct osl_table *audio_file_table;
-static char *status_items;
-static char *parser_friendly_status_items;
-
-/** The different sorting methods of the ls command. */
-enum ls_sorting_method {
-       /** -sp (default) */
-       LS_SORT_BY_PATH,
-       /** -ss */
-       LS_SORT_BY_SCORE,
-       /** -sl */
-       LS_SORT_BY_LAST_PLAYED,
-       /** -sn */
-       LS_SORT_BY_NUM_PLAYED,
-       /** -sf */
-       LS_SORT_BY_FREQUENCY,
-       /** -sc */
-       LS_SORT_BY_CHANNELS,
-       /** -si */
-       LS_SORT_BY_IMAGE_ID,
-       /** -sy */
-       LS_SORT_BY_LYRICS_ID,
-       /** -sb */
-       LS_SORT_BY_BITRATE,
-       /** -sd */
-       LS_SORT_BY_DURATION,
-       /** -sa */
-       LS_SORT_BY_AUDIO_FORMAT,
-       /** -sh */
-       LS_SORT_BY_HASH,
-};
-
-/** The different listing modes of the ls command. */
-enum ls_listing_mode {
-       /** Default listing mode. */
-       LS_MODE_SHORT,
-       /** -l or -ll */
-       LS_MODE_LONG,
-       /** -lv */
-       LS_MODE_VERBOSE,
-       /** -lm */
-       LS_MODE_MBOX,
-       /** -lc */
-       LS_MODE_CHUNKS,
-       /** -lp */
-       LS_MODE_PARSER,
-};
-
 /* Data about one audio file. Needed for ls and stat output. */
 struct ls_data {
        /* Usual audio format handler information. */
@@ -86,16 +36,42 @@ struct ls_data {
        unsigned char *hash;
 };
 
-/** The flags accepted by the ls command. */
-enum ls_flags {
-       /** -p */
-       LS_FLAG_FULL_PATH = 1,
-       /** -a */
-       LS_FLAG_ADMISSIBLE_ONLY = 2,
-       /** -r */
-       LS_FLAG_REVERSE = 4,
-       /** -d */
-       LS_FLAG_UNIXDATE = 8,
+/*
+ * The internal state of the audio file table is described by the following
+ * variables which are private to aft.c.
+ */
+static struct osl_table *audio_file_table; /* NULL if table not open */
+static struct osl_row *current_aft_row; /* NULL if no audio file open */
+static unsigned char current_hash[HASH_SIZE]; /* only used on sighup */
+
+static char *status_items;
+static char *parser_friendly_status_items;
+static struct ls_data status_item_ls_data;
+
+/** The different sorting methods of the ls command. */
+enum ls_sorting_method {
+       LS_SORT_BY_PATH, /**< -s=p (default) */
+       LS_SORT_BY_SCORE, /**< -s=s */
+       LS_SORT_BY_LAST_PLAYED, /**< -s=l */
+       LS_SORT_BY_NUM_PLAYED, /**< -s=n */
+       LS_SORT_BY_FREQUENCY, /**< -s=f */
+       LS_SORT_BY_CHANNELS, /**< -s=c */
+       LS_SORT_BY_IMAGE_ID, /**< -s=i */
+       LS_SORT_BY_LYRICS_ID, /**< -s=y */
+       LS_SORT_BY_BITRATE, /**< -s=b */
+       LS_SORT_BY_DURATION, /**< -s=d */
+       LS_SORT_BY_AUDIO_FORMAT, /**< -s=a */
+       LS_SORT_BY_HASH, /**< -s=h */
+};
+
+/** The different listing modes of the ls command. */
+enum ls_listing_mode {
+       LS_MODE_SHORT, /**< Default listing mode. */
+       LS_MODE_LONG, /**< -l or -l=l */
+       LS_MODE_VERBOSE, /** -l=v */
+       LS_MODE_MBOX, /** -l=m */
+       LS_MODE_CHUNKS, /** -l=c */
+       LS_MODE_PARSER, /** -l=p */
 };
 
 /**
@@ -128,16 +104,11 @@ struct ls_widths {
 
 /** Data passed from the ls command handler to its callback function. */
 struct ls_options {
-       /** The given command line flags. */
-       unsigned flags;
-       /** The sorting method given at the command line. */
+       struct lls_parse_result *lpr;
+       /* Derived from lpr */
        enum ls_sorting_method sorting;
-       /** The given listing mode (short, long, verbose, mbox). */
+       /* Derived from lpr */
        enum ls_listing_mode mode;
-       /** The arguments passed to the ls command. */
-       char **patterns;
-       /** Number of non-option arguments. */
-       int num_patterns;
        /** Used for long listing mode to align the output fields. */
        struct ls_widths widths;
        /** Size of the \a data array. */
@@ -153,7 +124,7 @@ struct ls_options {
 /**
  * Describes the layout of the mmapped-afs info struct.
  *
- * \sa struct afs_info.
+ * \sa struct \ref afs_info.
  */
 enum afsi_offsets {
        /** Where .last_played is stored. */
@@ -182,7 +153,7 @@ enum afsi_offsets {
  * \param afsi Pointer to the audio file info to be converted.
  * \param obj Result pointer.
  *
- * \sa load_afsi().
+ * \sa \ref load_afsi().
  */
 static void save_afsi(struct afs_info *afsi, struct osl_object *obj)
 {
@@ -207,7 +178,7 @@ static void save_afsi(struct afs_info *afsi, struct osl_object *obj)
  *
  * \return Standard.
  *
- * \sa save_afsi().
+ * \sa \ref save_afsi().
  */
 static int load_afsi(struct afs_info *afsi, struct osl_object *obj)
 {
@@ -335,8 +306,8 @@ enum afhi_offsets {
        CHUNKS_TOTAL_OFFSET = 20,
        /** The length of the audio file header (4 bytes). */
        HEADER_LEN_OFFSET = 24,
-       /** Was: The start of the audio file header (4 bytes). */
-       AFHI_UNUSED2_OFFSET = 28,
+       /** Size of the largest chunk in bytes. (4 bytes). */
+       AFHI_MAX_CHUNK_SIZE_OFFSET = 28,
        /** The seconds part of the chunk time (4 bytes). */
        CHUNK_TV_TV_SEC_OFFSET = 32,
        /** The microseconds part of the chunk time (4 bytes). */
@@ -376,11 +347,14 @@ static void save_afhi(struct afh_info *afhi, char *buf)
        write_u8(buf + AFHI_CHANNELS_OFFSET, afhi->channels);
        write_u32(buf + CHUNKS_TOTAL_OFFSET, afhi->chunks_total);
        write_u32(buf + HEADER_LEN_OFFSET, afhi->header_len);
-       write_u32(buf + AFHI_UNUSED2_OFFSET, 0);
+       write_u32(buf + AFHI_MAX_CHUNK_SIZE_OFFSET, afhi->max_chunk_size);
        write_u32(buf + CHUNK_TV_TV_SEC_OFFSET, afhi->chunk_tv.tv_sec);
        write_u32(buf + CHUNK_TV_TV_USEC_OFFSET, afhi->chunk_tv.tv_usec);
        p = buf + AFHI_INFO_STRING_OFFSET;
-       /* The sprintf's below are OK as our caller made sure that buf is large enough */
+       /*
+        * The below sprintf(3) calls are OK because our caller already made
+        * sure that buf is large enough.
+        */
        p += sprintf(p, "%s", afhi->techinfo) + 1;
        p += sprintf(p, "%s", afhi->tags.artist) + 1;
        p += sprintf(p, "%s", afhi->tags.title) + 1;
@@ -389,6 +363,7 @@ static void save_afhi(struct afh_info *afhi, char *buf)
        sprintf(p, "%s", afhi->tags.comment);
 }
 
+/* does not load the chunk table */
 static void load_afhi(const char *buf, struct afh_info *afhi)
 {
        afhi->seconds_total = read_u32(buf + AFHI_SECONDS_TOTAL_OFFSET);
@@ -398,6 +373,7 @@ static void load_afhi(const char *buf, struct afh_info *afhi)
        afhi->channels = read_u8(buf + AFHI_CHANNELS_OFFSET);
        afhi->chunks_total = read_u32(buf + CHUNKS_TOTAL_OFFSET);
        afhi->header_len = read_u32(buf + HEADER_LEN_OFFSET);
+       afhi->max_chunk_size = read_u32(buf + AFHI_MAX_CHUNK_SIZE_OFFSET);
        afhi->chunk_tv.tv_sec = read_u32(buf + CHUNK_TV_TV_SEC_OFFSET);
        afhi->chunk_tv.tv_usec = read_u32(buf + CHUNK_TV_TV_USEC_OFFSET);
        afhi->techinfo = (char *)buf + AFHI_INFO_STRING_OFFSET;
@@ -408,42 +384,37 @@ static void load_afhi(const char *buf, struct afh_info *afhi)
        afhi->tags.comment = afhi->tags.album + strlen(afhi->tags.album) + 1;
 }
 
+/* Only used for saving the chunk table, but not for loading. */
 static unsigned sizeof_chunk_table(struct afh_info *afhi)
 {
-       if (!afhi)
+       if (!afhi || !afhi->chunk_table)
                return 0;
        return 4 * (afhi->chunks_total + 1);
 }
 
-static uint32_t save_chunk_table(struct afh_info *afhi, char *buf)
+static void save_chunk_table(struct afh_info *afhi, char *buf)
 {
-       int i;
-       uint32_t max = 0, old = 0;
+       uint32_t n;
 
-       for (i = 0; i <= afhi->chunks_total; i++) {
-               uint32_t val = afhi->chunk_table[i];
-               write_u32(buf + 4 * i, val);
-               /*
-                * If the first chunk is the header, do not consider it for the
-                * calculation of the largest chunk size.
-                */
-               if (i == 0 || (i == 1 && afhi->header_len > 0)) {
-                       old = val;
-                       continue;
-               }
-               max = PARA_MAX(max, val - old);
-               old = val;
-       }
-       return max;
+       if (!afhi->chunk_table || afhi->chunks_total == 0)
+               return;
+       for (n = 0; n <= afhi->chunks_total; n++)
+               write_u32(buf + 4 * n, afhi->chunk_table[n]);
 }
 
-static void load_chunk_table(struct afh_info *afhi, char *buf)
+static void load_chunk_table(struct afh_info *afhi, const struct osl_object *ct)
 {
        int i;
+       size_t sz;
 
-       afhi->chunk_table = para_malloc(sizeof_chunk_table(afhi));
-       for (i = 0; i <= afhi->chunks_total; i++)
-               afhi->chunk_table[i] = read_u32(buf + 4 * i);
+       if (!ct->data || ct->size < 4 * (afhi->chunks_total + 1)) {
+               afhi->chunk_table = NULL;
+               return;
+       }
+       sz  = PARA_MIN(((size_t)afhi->chunks_total + 1) * 4, ct->size) + 1;
+       afhi->chunk_table = para_malloc(sz);
+       for (i = 0; i <= afhi->chunks_total && i * 4 + 3 < ct->size; i++)
+               afhi->chunk_table[i] = read_u32(ct->data + 4 * i);
 }
 
 /**
@@ -547,7 +518,7 @@ static int get_afsi_of_path(const char *path, struct afs_info *afsi)
  * \param row Pointer to a row in the audio file table.
  * \param path Result pointer.
  *
- * The result is a pointer to mmapped data. The caller must not attempt
+ * The result is a pointer to memory-mapped data. The caller must not attempt
  * to free it.
  *
  * \return Standard.
@@ -557,10 +528,12 @@ int get_audio_file_path_of_row(const struct osl_row *row, char **path)
        struct osl_object path_obj;
        int ret = osl(osl_get_object(audio_file_table, row, AFTCOL_PATH,
                &path_obj));
+
        if (ret < 0)
-               return ret;
-       *path = path_obj.data;
-       return 1;
+               *path = NULL;
+       else
+               *path = path_obj.data;
+       return ret;
 }
 
 /**
@@ -571,7 +544,7 @@ int get_audio_file_path_of_row(const struct osl_row *row, char **path)
  *
  * \return The return value of the underlying call to osl_get_object().
  *
- * \sa get_hash_of_row().
+ * \sa \ref get_hash_of_row().
  */
 static int get_hash_object_of_aft_row(const struct osl_row *row,
                struct osl_object *obj)
@@ -609,13 +582,17 @@ static int get_hash_of_row(const struct osl_row *row, unsigned char **hash)
  *
  * \return The return value of the underlying call to osl_get_object().
  *
- * \sa get_chunk_table_of_row().
+ * After the call the members of the afhi structure point to mapped memory
+ * which is owned by the osl table, Hence the caller must not attempt to free
+ * this memory by calling \ref clear_afhi().
  */
 int get_afhi_of_row(const struct osl_row *row, struct afh_info *afhi)
 {
        struct osl_object obj;
-       int ret = osl(osl_get_object(audio_file_table, row, AFTCOL_AFHI,
-               &obj));
+       int ret;
+
+       assert(row);
+       ret = osl(osl_get_object(audio_file_table, row, AFTCOL_AFHI, &obj));
        if (ret < 0)
                return ret;
        load_afhi(obj.data, afhi);
@@ -638,7 +615,13 @@ static int save_afd(struct audio_file_data *afd)
                goto err;
        buf = shm_afd;
        buf += sizeof(*afd);
-       afd->max_chunk_size = save_chunk_table(&afd->afhi, buf);
+       save_chunk_table(&afd->afhi, buf);
+       if (afd->afhi.max_chunk_size == 0) { /* v0.5.x on-disk afhi */
+               set_max_chunk_size(&afd->afhi);
+               PARA_NOTICE_LOG("max chunk size unset, re-add required\n");
+       } else
+               PARA_INFO_LOG("using max chunk size from afhi\n");
+       afd->max_chunk_size = afd->afhi.max_chunk_size;
        *(struct audio_file_data *)shm_afd = *afd;
        shm_detach(shm_afd);
        return shmid;
@@ -663,14 +646,22 @@ int load_afd(int shmid, struct audio_file_data *afd)
 {
        void *shm_afd;
        int ret;
+       struct osl_object obj;
 
        ret = shm_attach(shmid, ATTACH_RO, &shm_afd);
        if (ret < 0)
                return ret;
+       ret = shm_size(shmid, &obj.size);
+       if (ret < 0)
+               goto detach;
        *afd = *(struct audio_file_data *)shm_afd;
-       load_chunk_table(&afd->afhi, shm_afd + sizeof(*afd));
+       obj.data = shm_afd + sizeof(*afd);
+       obj.size -= sizeof(*afd);
+       load_chunk_table(&afd->afhi, &obj);
+       ret = 1;
+detach:
        shm_detach(shm_afd);
-       return 1;
+       return ret;
 }
 
 static int get_local_time(uint64_t *seconds, char *buf, size_t size,
@@ -699,7 +690,7 @@ static int get_local_time(uint64_t *seconds, char *buf, size_t size,
        /*
         * If the given time is more than six month away from the current time,
         * we print only the year. The additional space character in the format
-        * string below makes the formated date align nicely with dates that
+        * string below makes the formatted date align nicely with dates that
         * contain the time (those written by the above strftime() statement).
         */
        if (!strftime(buf, size, "%b %e  %Y", tm))
@@ -730,7 +721,8 @@ __a_const static short unsigned get_duration_width(int seconds)
        return width + 6;
 }
 
-static void get_duration_buf(int seconds, char *buf, struct ls_options *opts)
+static void get_duration_buf(int seconds, char *buf, size_t bufsize,
+               struct ls_options *opts)
 {
        unsigned hours = seconds / 3600, mins = (seconds % 3600) / 60;
        short unsigned max_width;
@@ -738,10 +730,12 @@ static void get_duration_buf(int seconds, char *buf, struct ls_options *opts)
        if (!hours) { /* m:ss or mm:ss */
                max_width = opts->mode == LS_MODE_LONG?
                        opts->widths.duration_width : 4;
+               assert(max_width < bufsize - 1);
                sprintf(buf, "%*u:%02d", max_width - 3, mins, seconds % 60);
        } else { /* more than one hour => h:mm:ss, hh:mm:ss, hhh:mm:ss, ... */
                max_width = opts->mode == LS_MODE_LONG?
                        opts->widths.duration_width : 7;
+               assert(max_width < bufsize - 1);
                sprintf(buf, "%*u:%02u:%02d", max_width - 6, hours, mins,
                        seconds % 60);
        }
@@ -753,11 +747,11 @@ static int write_attribute_items(struct para_buffer *b,
        char *att_text;
        int ret;
 
-       WRITE_STATUS_ITEM(b, SI_ATTRIBUTES_BITMAP, "%s\n", att_bitmap);
+       WRITE_STATUS_ITEM(b, SI_attributes_bitmap, "%s\n", att_bitmap);
        ret = get_attribute_text(&afsi->attributes, " ", &att_text);
        if (ret < 0)
                return ret;
-       WRITE_STATUS_ITEM(b, SI_ATTRIBUTES_TXT, "%s\n", att_text);
+       WRITE_STATUS_ITEM(b, SI_attributes_txt, "%s\n", att_text);
        free(att_text);
        return ret;
 }
@@ -766,9 +760,9 @@ static void write_lyrics_items(struct para_buffer *b, struct afs_info *afsi)
 {
        char *lyrics_name;
 
-       WRITE_STATUS_ITEM(b, SI_LYRICS_ID, "%u\n", afsi->lyrics_id);
+       WRITE_STATUS_ITEM(b, SI_lyrics_id, "%u\n", afsi->lyrics_id);
        lyr_get_name_by_id(afsi->lyrics_id, &lyrics_name);
-       WRITE_STATUS_ITEM(b, SI_LYRICS_NAME, "%s\n", lyrics_name?
+       WRITE_STATUS_ITEM(b, SI_lyrics_name, "%s\n", lyrics_name?
                lyrics_name : "(none)");
 }
 
@@ -776,26 +770,26 @@ static void write_image_items(struct para_buffer *b, struct afs_info *afsi)
 {
        char *image_name;
 
-       WRITE_STATUS_ITEM(b, SI_IMAGE_ID, "%u\n", afsi->image_id);
+       WRITE_STATUS_ITEM(b, SI_image_id, "%u\n", afsi->image_id);
        img_get_name_by_id(afsi->image_id, &image_name);
-       WRITE_STATUS_ITEM(b, SI_IMAGE_NAME, "%s\n", image_name?
+       WRITE_STATUS_ITEM(b, SI_image_name, "%s\n", image_name?
                image_name : "(none)");
 }
 
 static void write_filename_items(struct para_buffer *b, const char *path,
-               unsigned flags)
+               bool basename)
 {
        char *val;
 
-       if (!(flags & LS_FLAG_FULL_PATH)) {
-               WRITE_STATUS_ITEM(b, SI_BASENAME, "%s\n", path);
+       if (basename) {
+               WRITE_STATUS_ITEM(b, SI_basename, "%s\n", path);
                return;
        }
-       WRITE_STATUS_ITEM(b, SI_PATH, "%s\n", path);
+       WRITE_STATUS_ITEM(b, SI_path, "%s\n", path);
        val = para_basename(path);
-       WRITE_STATUS_ITEM(b, SI_BASENAME, "%s\n", val? val : "");
+       WRITE_STATUS_ITEM(b, SI_basename, "%s\n", val? val : "");
        val = para_dirname(path);
-       WRITE_STATUS_ITEM(b, SI_DIRECTORY, "%s\n", val? val : "");
+       WRITE_STATUS_ITEM(b, SI_directory, "%s\n", val? val : "");
        free(val);
 }
 
@@ -820,7 +814,11 @@ static int print_chunk_table(struct ls_data *d, struct para_buffer *b)
                (long unsigned) d->afhi.chunk_tv.tv_usec
        );
        buf = chunk_table_obj.data;
-       for (i = 0; i <= d->afhi.chunks_total; i++)
+       for (
+               i = 0;
+               i <= d->afhi.chunks_total && 4 * i + 3 < chunk_table_obj.size;
+               i++
+       )
                para_printf(b, "%u ", (unsigned) read_u32(buf + 4 * i));
        para_printf(b, "\n");
        ret = 1;
@@ -828,17 +826,12 @@ static int print_chunk_table(struct ls_data *d, struct para_buffer *b)
        return ret;
 }
 
-static void write_score(struct para_buffer *b, struct ls_data *d,
-               struct ls_options *opts)
-{
-       if (!(opts->flags & LS_FLAG_ADMISSIBLE_ONLY)) /* no score*/
-               return;
-       WRITE_STATUS_ITEM(b, SI_SCORE, "%li\n", d->score);
-}
-
 static int print_list_item(struct ls_data *d, struct ls_options *opts,
        struct para_buffer *b, time_t current_time)
 {
+       const struct lls_opt_result *r_a = SERVER_CMD_OPT_RESULT(LS, ADMISSIBLE, opts->lpr);
+       const struct lls_opt_result *r_b = SERVER_CMD_OPT_RESULT(LS, BASENAME, opts->lpr);
+       const struct lls_opt_result *r_d = SERVER_CMD_OPT_RESULT(LS, UNIX_DATE, opts->lpr);
        int ret;
        char att_buf[65];
        char last_played_time[30];
@@ -857,7 +850,7 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts,
                goto out;
        }
        get_attribute_bitmap(&afsi->attributes, att_buf);
-       if (opts->flags & LS_FLAG_UNIXDATE)
+       if (lls_opt_given(r_d))
                sprintf(last_played_time, "%llu",
                        (long long unsigned)afsi->last_played);
        else {
@@ -866,13 +859,13 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts,
                if (ret < 0)
                        goto out;
        }
-       get_duration_buf(afhi->seconds_total, duration_buf, opts);
+       get_duration_buf(afhi->seconds_total, duration_buf,
+               sizeof(duration_buf), opts);
        if (opts->mode == LS_MODE_LONG) {
                struct ls_widths *w = &opts->widths;
-               if (opts->flags & LS_FLAG_ADMISSIBLE_ONLY) {
+               if (lls_opt_given(r_a))
                        para_printf(b, "%*li ", opts->widths.score_width,
                                d->score);
-               }
                para_printf(b,
                        "%s "   /* attributes */
                        "%*u "  /* amp */
@@ -912,35 +905,38 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts,
                        last_played_time,
                        bn? bn : "?");
        }
-       write_filename_items(b, d->path, opts->flags);
-       write_score(b, d, opts);
+       write_filename_items(b, d->path, lls_opt_given(r_b));
+       if (lls_opt_given(r_a))
+               WRITE_STATUS_ITEM(b, SI_score, "%li\n", d->score);
        ret = write_attribute_items(b, att_buf, afsi);
        if (ret < 0)
                goto out;
        write_image_items(b, afsi);
        write_lyrics_items(b, afsi);
        hash_to_asc(d->hash, asc_hash);
-       WRITE_STATUS_ITEM(b, SI_HASH, "%s\n", asc_hash);
-       WRITE_STATUS_ITEM(b, SI_BITRATE, "%dkbit/s\n", afhi->bitrate);
-       WRITE_STATUS_ITEM(b, SI_FORMAT, "%s\n",
+       WRITE_STATUS_ITEM(b, SI_hash, "%s\n", asc_hash);
+       WRITE_STATUS_ITEM(b, SI_bitrate, "%dkbit/s\n", afhi->bitrate);
+       WRITE_STATUS_ITEM(b, SI_format, "%s\n",
                audio_format_name(afsi->audio_format_id));
-       WRITE_STATUS_ITEM(b, SI_FREQUENCY, "%dHz\n", afhi->frequency);
-       WRITE_STATUS_ITEM(b, SI_CHANNELS, "%d\n", afhi->channels);
-       WRITE_STATUS_ITEM(b, SI_DURATION, "%s\n", duration_buf);
-       WRITE_STATUS_ITEM(b, SI_SECONDS_TOTAL, "%" PRIu32 "\n",
+       WRITE_STATUS_ITEM(b, SI_frequency, "%dHz\n", afhi->frequency);
+       WRITE_STATUS_ITEM(b, SI_channels, "%d\n", afhi->channels);
+       WRITE_STATUS_ITEM(b, SI_duration, "%s\n", duration_buf);
+       WRITE_STATUS_ITEM(b, SI_seconds_total, "%" PRIu32 "\n",
                afhi->seconds_total);
-       WRITE_STATUS_ITEM(b, SI_LAST_PLAYED, "%s\n", last_played_time);
-       WRITE_STATUS_ITEM(b, SI_NUM_PLAYED, "%u\n", afsi->num_played);
-       WRITE_STATUS_ITEM(b, SI_AMPLIFICATION, "%u\n", afsi->amp);
-       WRITE_STATUS_ITEM(b, SI_CHUNK_TIME, "%lu\n", tv2ms(&afhi->chunk_tv));
-       WRITE_STATUS_ITEM(b, SI_NUM_CHUNKS, "%" PRIu32 "\n",
+       WRITE_STATUS_ITEM(b, SI_last_played, "%s\n", last_played_time);
+       WRITE_STATUS_ITEM(b, SI_num_played, "%u\n", afsi->num_played);
+       WRITE_STATUS_ITEM(b, SI_amplification, "%u\n", afsi->amp);
+       WRITE_STATUS_ITEM(b, SI_chunk_time, "%lu\n", tv2ms(&afhi->chunk_tv));
+       WRITE_STATUS_ITEM(b, SI_num_chunks, "%" PRIu32 "\n",
                afhi->chunks_total);
-       WRITE_STATUS_ITEM(b, SI_TECHINFO, "%s\n", afhi->techinfo);
-       WRITE_STATUS_ITEM(b, SI_ARTIST, "%s\n", afhi->tags.artist);
-       WRITE_STATUS_ITEM(b, SI_TITLE, "%s\n", afhi->tags.title);
-       WRITE_STATUS_ITEM(b, SI_YEAR, "%s\n", afhi->tags.year);
-       WRITE_STATUS_ITEM(b, SI_ALBUM, "%s\n", afhi->tags.album);
-       WRITE_STATUS_ITEM(b, SI_COMMENT, "%s\n", afhi->tags.comment);
+       WRITE_STATUS_ITEM(b, SI_max_chunk_size, "%" PRIu32 "\n",
+               afhi->max_chunk_size);
+       WRITE_STATUS_ITEM(b, SI_techinfo, "%s\n", afhi->techinfo);
+       WRITE_STATUS_ITEM(b, SI_artist, "%s\n", afhi->tags.artist);
+       WRITE_STATUS_ITEM(b, SI_title, "%s\n", afhi->tags.title);
+       WRITE_STATUS_ITEM(b, SI_year, "%s\n", afhi->tags.year);
+       WRITE_STATUS_ITEM(b, SI_album, "%s\n", afhi->tags.album);
+       WRITE_STATUS_ITEM(b, SI_comment, "%s\n", afhi->tags.comment);
        if (opts->mode == LS_MODE_MBOX) {
                struct osl_object lyrics_def;
                lyr_get_def_by_id(afsi->lyrics_id, &lyrics_def);
@@ -954,9 +950,6 @@ out:
        return ret;
 }
 
-static struct ls_data status_item_ls_data;
-static struct osl_row *current_aft_row;
-
 static void make_inode_status_items(struct para_buffer *pb)
 {
        struct stat statbuf = {.st_size = 0};
@@ -974,46 +967,71 @@ static void make_inode_status_items(struct para_buffer *pb)
        ret = strftime(mtime_str, 29, "%b %d %Y", &mtime_tm);
        assert(ret > 0); /* number of bytes placed in mtime_str */
 out:
-       WRITE_STATUS_ITEM(pb, SI_MTIME, "%s\n", mtime_str);
-       WRITE_STATUS_ITEM(pb, SI_FILE_SIZE, "%ld\n", statbuf.st_size / 1024);
+       WRITE_STATUS_ITEM(pb, SI_mtime, "%s\n", mtime_str);
+       WRITE_STATUS_ITEM(pb, SI_file_size, "%ld\n", statbuf.st_size / 1024);
+}
+
+/**
+ * Deallocate and invalidate the status item strings.
+ *
+ * This needs to be a public function so that afs.c can call it on shutdown.
+ */
+void free_status_items(void)
+{
+       freep(&status_items);
+       freep(&parser_friendly_status_items);
 }
 
-static int make_status_items(void)
+static void make_status_items(void)
 {
-       struct ls_options opts = {
-               .flags = LS_FLAG_FULL_PATH | LS_FLAG_ADMISSIBLE_ONLY,
-               .mode = LS_MODE_VERBOSE,
-       };
+       const struct lls_command *cmd = SERVER_CMD_CMD_PTR(LS);
+       char *argv[] = {"ls", "--admissible", "--listing-mode=verbose"};
+       struct ls_options opts = {.mode = LS_MODE_VERBOSE};
        struct para_buffer pb = {.max_size = shm_get_shmmax() - 1};
        time_t current_time;
        int ret;
 
+       free_status_items();
+       if (!status_item_ls_data.path) /* no audio file open */
+               return;
+       ret = lls_parse(ARRAY_SIZE(argv), argv, cmd, &opts.lpr, NULL);
+       assert(ret >= 0);
        time(&current_time);
        ret = print_list_item(&status_item_ls_data, &opts, &pb, current_time);
        if (ret < 0)
-               return ret;
+               goto out;
        make_inode_status_items(&pb);
-       free(status_items);
        status_items = pb.buf;
+
        memset(&pb, 0, sizeof(pb));
        pb.max_size = shm_get_shmmax() - 1;
        pb.flags = PBF_SIZE_PREFIX;
        ret = print_list_item(&status_item_ls_data, &opts, &pb, current_time);
-       if (ret < 0) {
-               free(status_items);
-               status_items = NULL;
-               return ret;
-       }
+       if (ret < 0)
+               goto out;
        make_inode_status_items(&pb);
-       free(parser_friendly_status_items);
        parser_friendly_status_items = pb.buf;
-       return 1;
+       ret = 1;
+out:
+       if (ret < 0) {
+               PARA_WARNING_LOG("could not create status items: %s\n",
+                       para_strerror(-ret));
+               free_status_items();
+       }
+       lls_free_parse_result(opts.lpr, cmd);
 }
 
 /**
  * Open the audio file with highest score and set up an afd structure.
  *
- * \param afd Result pointer.
+ * This determines and opens the next audio file, verifies that it did not
+ * change by comparing the recomputed the hash value of the file contents
+ * against the value stored in the audio file table. If all goes well, it
+ * creates a shared memory area containing the serialized version of the afd
+ * structure, including the chunk table, if any. The caller can then send the
+ * ID of this area and the open fd to the server process.
+ *
+ * \param fd Result pointer for the file descriptor of the audio file.
  *
  * On success, the numplayed field of the audio file selector info is increased
  * and the lastplayed time is set to the current time. Finally, the score of
@@ -1021,7 +1039,7 @@ static int make_status_items(void)
  *
  * \return Positive shmid on success, negative on errors.
  */
-int open_and_update_audio_file(struct audio_file_data *afd)
+int open_and_update_audio_file(int *fd)
 {
        unsigned char file_hash[HASH_SIZE];
        struct osl_object afsi_obj;
@@ -1030,33 +1048,57 @@ int open_and_update_audio_file(struct audio_file_data *afd)
        struct afsi_change_event_data aced;
        struct osl_object map, chunk_table_obj;
        struct ls_data *d = &status_item_ls_data;
+       unsigned char *tmp_hash;
+       struct audio_file_data afd;
 again:
        ret = score_get_best(&current_aft_row, &d->score);
        if (ret < 0)
                return ret;
-       ret = get_hash_of_row(current_aft_row, &d->hash);
+       /*
+        * get_hash_of_row() and get_audio_file_path_of_row() initialize
+        * their pointer argument to point to memory-mapped files. These pointers
+        * become stale after a new audio file has been added or after the
+        * server process received SIGHUP. For in both cases libosl unmaps and
+        * remaps the underlying database files, and this remapping may well
+        * change the starting address of the mapping. To avoid stale pointer
+        * references we create copies on the heap.
+        */
+       ret = get_hash_of_row(current_aft_row, &tmp_hash);
        if (ret < 0)
                return ret;
+       if (!d->hash)
+               d->hash = para_malloc(HASH_SIZE);
+       memcpy(d->hash, tmp_hash, HASH_SIZE);
+       free(d->path);
        ret = get_audio_file_path_of_row(current_aft_row, &d->path);
        if (ret < 0)
                return ret;
        PARA_NOTICE_LOG("%s\n", d->path);
+       d->path = para_strdup(d->path);
+
        ret = get_afsi_object_of_row(current_aft_row, &afsi_obj);
        if (ret < 0)
                return ret;
        ret = load_afsi(&d->afsi, &afsi_obj);
        if (ret < 0)
                return ret;
-       ret = get_afhi_of_row(current_aft_row, &afd->afhi);
+       ret = get_afhi_of_row(current_aft_row, &afd.afhi);
        if (ret < 0)
                return ret;
-       d->afhi = afd->afhi;
-       d->afhi.chunk_table = afd->afhi.chunk_table = NULL;
+       d->afhi = afd.afhi;
+       d->afhi.chunk_table = afd.afhi.chunk_table = NULL;
        ret = osl(osl_open_disk_object(audio_file_table, current_aft_row,
                AFTCOL_CHUNKS, &chunk_table_obj));
-       if (ret < 0)
-               return ret;
-       ret = mmap_full_file(d->path, O_RDONLY, &map.data, &map.size, &afd->fd);
+       if (ret < 0) {
+               if (!afh_supports_dynamic_chunks(d->afsi.audio_format_id))
+                       return ret;
+               PARA_INFO_LOG("no chunk table for %s\n", d->path);
+               chunk_table_obj.data = NULL;
+               chunk_table_obj.size = 0;
+       } else {
+               PARA_INFO_LOG("chunk table: %zu bytes\n", chunk_table_obj.size);
+       }
+       ret = mmap_full_file(d->path, O_RDONLY, &map.data, &map.size, fd);
        if (ret < 0)
                goto out;
        hash_function(map.data, map.size, file_hash);
@@ -1071,8 +1113,8 @@ again:
        new_afsi.last_played = time(NULL);
        save_afsi(&new_afsi, &afsi_obj); /* in-place update */
 
-       afd->audio_format_id = d->afsi.audio_format_id;
-       load_chunk_table(&afd->afhi, chunk_table_obj.data);
+       afd.audio_format_id = d->afsi.audio_format_id;
+       load_chunk_table(&afd.afhi, &chunk_table_obj);
        aced.aft_row = current_aft_row;
        aced.old_afsi = &d->afsi;
        /*
@@ -1082,10 +1124,11 @@ again:
        ret = afs_event(AFSI_CHANGE, NULL, &aced);
        if (ret < 0)
                goto out;
-       ret = save_afd(afd);
+       ret = save_afd(&afd);
 out:
-       free(afd->afhi.chunk_table);
-       osl_close_disk_object(&chunk_table_obj);
+       free(afd.afhi.chunk_table);
+       if (chunk_table_obj.data)
+               osl_close_disk_object(&chunk_table_obj);
        if (ret < 0) {
                PARA_ERROR_LOG("%s: %s\n", d->path, para_strerror(-ret));
                ret = score_delete(current_aft_row);
@@ -1095,6 +1138,12 @@ out:
        return ret;
 }
 
+static int ls_hash_compare(const void *a, const void *b)
+{
+       struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b;
+       return memcmp(d1->hash, d2->hash, HASH_SIZE);
+}
+
 static int ls_audio_format_compare(const void *a, const void *b)
 {
        struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b;
@@ -1161,8 +1210,16 @@ static int ls_path_compare(const void *a, const void *b)
        return strcmp(d1->path, d2->path);
 }
 
+static inline bool admissible_only(struct ls_options *opts)
+{
+       return SERVER_CMD_OPT_GIVEN(LS, ADMISSIBLE, opts->lpr)
+               || opts->sorting == LS_SORT_BY_SCORE;
+}
+
 static int sort_matching_paths(struct ls_options *options)
 {
+       const struct lls_opt_result *r_b = SERVER_CMD_OPT_RESULT(LS, BASENAME,
+               options->lpr);
        size_t nmemb = options->num_matching_paths;
        size_t size = sizeof(*options->data_ptr);
        int (*compar)(const void *, const void *);
@@ -1173,13 +1230,13 @@ static int sort_matching_paths(struct ls_options *options)
                options->data_ptr[i] = options->data + i;
 
        /* In these cases the array is already sorted */
-       if (options->sorting == LS_SORT_BY_PATH
-               && !(options->flags & LS_FLAG_ADMISSIBLE_ONLY)
-               && (options->flags & LS_FLAG_FULL_PATH))
-               return 1;
-       if (options->sorting == LS_SORT_BY_SCORE &&
-                       options->flags & LS_FLAG_ADMISSIBLE_ONLY)
-               return 1;
+       if (admissible_only(options)) {
+               if (options->sorting == LS_SORT_BY_SCORE)
+                       return 1;
+       } else {
+               if (options->sorting == LS_SORT_BY_PATH && !lls_opt_given(r_b))
+                       return 1;
+       }
 
        switch (options->sorting) {
        case LS_SORT_BY_PATH:
@@ -1204,6 +1261,8 @@ static int sort_matching_paths(struct ls_options *options)
                compar = ls_duration_compare; break;
        case LS_SORT_BY_AUDIO_FORMAT:
                compar = ls_audio_format_compare; break;
+       case LS_SORT_BY_HASH:
+               compar = ls_hash_compare; break;
        default:
                return -E_BAD_SORT;
        }
@@ -1217,15 +1276,16 @@ static int prepare_ls_row(struct osl_row *row, void *ls_opts)
 {
        int ret, i;
        struct ls_options *options = ls_opts;
+       bool basename_given = SERVER_CMD_OPT_GIVEN(LS, BASENAME, options->lpr);
        struct ls_data *d;
        struct ls_widths *w;
        unsigned short num_digits;
-       unsigned tmp;
+       unsigned tmp, num_inputs;
        struct osl_row *aft_row;
        long score;
        char *path;
 
-       if (options->flags & LS_FLAG_ADMISSIBLE_ONLY) {
+       if (admissible_only(options)) {
                ret = get_score_and_aft_row(row, &score, &aft_row);
                if (ret < 0)
                        return ret;
@@ -1236,21 +1296,22 @@ static int prepare_ls_row(struct osl_row *row, void *ls_opts)
        ret = get_audio_file_path_of_row(aft_row, &path);
        if (ret < 0)
                return ret;
-       if (!(options->flags & LS_FLAG_FULL_PATH)) {
+       if (basename_given) {
                char *p = strrchr(path, '/');
                if (p)
                        path = p + 1;
        }
-       if (options->num_patterns) {
-               for (i = 0; i < options->num_patterns; i++) {
-                       ret = fnmatch(options->patterns[i], path, 0);
+       num_inputs = lls_num_inputs(options->lpr);
+       if (num_inputs > 0) {
+               for (i = 0; i < num_inputs; i++) {
+                       ret = fnmatch(lls_input(i, options->lpr), path, 0);
                        if (!ret)
                                break;
                        if (ret == FNM_NOMATCH)
                                continue;
                        return -E_FNMATCH;
                }
-               if (i >= options->num_patterns) /* no match */
+               if (i >= num_inputs) /* no match */
                        return 1;
        }
        tmp = options->num_matching_paths++;
@@ -1289,7 +1350,7 @@ static int prepare_ls_row(struct osl_row *row, void *ls_opts)
        w->amp_width = PARA_MAX(w->amp_width, num_digits);
        num_digits = strlen(audio_format_name(d->afsi.audio_format_id));
        w->audio_format_width = PARA_MAX(w->audio_format_width, num_digits);
-       if (options->flags & LS_FLAG_ADMISSIBLE_ONLY) {
+       if (admissible_only(options)) {
                GET_NUM_DIGITS(score, &num_digits);
                num_digits++; /* add one for the sign (space or "-") */
                w->score_width = PARA_MAX(w->score_width, num_digits);
@@ -1302,21 +1363,19 @@ err:
 
 static int com_ls_callback(struct afs_callback_arg *aca)
 {
+       const struct lls_command *cmd = SERVER_CMD_CMD_PTR(LS);
        struct ls_options *opts = aca->query.data;
-       char *p, *pattern_start = (char *)aca->query.data + sizeof(*opts);
        int i = 0, ret;
        time_t current_time;
+       const struct lls_opt_result *r_r;
+
+       ret = lls_deserialize_parse_result(
+               (char *)aca->query.data + sizeof(*opts), cmd, &opts->lpr);
+       assert(ret >= 0);
+       r_r = SERVER_CMD_OPT_RESULT(LS, REVERSE, opts->lpr);
 
        aca->pbout.flags = (opts->mode == LS_MODE_PARSER)? PBF_SIZE_PREFIX : 0;
-       if (opts->num_patterns) {
-               opts->patterns = para_malloc(opts->num_patterns * sizeof(char *));
-               for (i = 0, p = pattern_start; i < opts->num_patterns; i++) {
-                       opts->patterns[i] = p;
-                       p += strlen(p) + 1;
-               }
-       } else
-               opts->patterns = NULL;
-       if (opts->flags & LS_FLAG_ADMISSIBLE_ONLY)
+       if (admissible_only(opts))
                ret = admissible_file_loop(opts, prepare_ls_row);
        else
                ret = osl(osl_rbtree_loop(audio_file_table, AFTCOL_PATH, opts,
@@ -1324,14 +1383,14 @@ static int com_ls_callback(struct afs_callback_arg *aca)
        if (ret < 0)
                goto out;
        if (opts->num_matching_paths == 0) {
-               ret = opts->num_patterns > 0? -E_NO_MATCH : 0;
+               ret = lls_num_inputs(opts->lpr) > 0? -E_NO_MATCH : 0;
                goto out;
        }
        ret = sort_matching_paths(opts);
        if (ret < 0)
                goto out;
        time(&current_time);
-       if (opts->flags & LS_FLAG_REVERSE)
+       if (lls_opt_given(r_r))
                for (i = opts->num_matching_paths - 1; i >= 0; i--) {
                        ret = print_list_item(opts->data_ptr[i], opts,
                                &aca->pbout, current_time);
@@ -1346,143 +1405,91 @@ static int com_ls_callback(struct afs_callback_arg *aca)
                                goto out;
                }
 out:
+       lls_free_parse_result(opts->lpr, cmd);
        free(opts->data);
        free(opts->data_ptr);
-       free(opts->patterns);
        return ret;
 }
 
-/*
- * TODO: flags -h (sort by hash)
- */
-int com_ls(struct command_context *cc)
+static int com_ls(struct command_context *cc, struct lls_parse_result *lpr)
 {
-       int i;
-       unsigned flags = 0;
-       enum ls_sorting_method sort = LS_SORT_BY_PATH;
-       enum ls_listing_mode mode = LS_MODE_SHORT;
-       struct ls_options opts = {.patterns = NULL};
-       struct osl_object query = {.data = &opts, .size = sizeof(opts)};
-
-       for (i = 1; i < cc->argc; i++) {
-               const char *arg = cc->argv[i];
-               if (arg[0] != '-')
-                       break;
-               if (!strcmp(arg, "--")) {
-                       i++;
-                       break;
-               }
-               /*
-                * Compatibility: Prior to 0.5.5 it was necessary to specify
-                * the listing mode without the '=' character as in -lv, for
-                * example. Now the variant with '=' is preferred and
-                * documented but we still accept the old way to specify the
-                * listing mode.
-                *
-                * Support for the legacy syntax can be dropped at 0.6.0
-                * or later.
-                */
-               if (!strncmp(arg, "-l", 2)) {
-                       arg += 2;
-                       if (*arg == '=')
-                               arg++;
-                       switch (*arg) {
-                       case 's':
-                               mode = LS_MODE_SHORT;
-                               continue;
-                       case 'l':
-                       case '\0':
-                               mode = LS_MODE_LONG;
-                               continue;
-                       case 'v':
-                               mode = LS_MODE_VERBOSE;
-                               continue;
-                       case 'm':
-                               mode = LS_MODE_MBOX;
-                               continue;
-                       case 'c':
-                               mode = LS_MODE_CHUNKS;
-                               continue;
-                       case 'p':
-                               mode = LS_MODE_PARSER;
-                               continue;
-                       default:
-                               return -E_AFT_SYNTAX;
-                       }
-               }
-               if (!strcmp(arg, "-p") || !strcmp(arg, "-F")) {
-                       flags |= LS_FLAG_FULL_PATH;
-                       continue;
-               }
-               if (!strcmp(arg, "-b")) {
-                       flags &= ~LS_FLAG_FULL_PATH;
-                       continue;
-               }
-               if (!strcmp(arg, "-a")) {
-                       flags |= LS_FLAG_ADMISSIBLE_ONLY;
-                       continue;
-               }
-               if (!strcmp(arg, "-r")) {
-                       flags |= LS_FLAG_REVERSE;
-                       continue;
-               }
-               if (!strcmp(arg, "-d")) {
-                       flags |= LS_FLAG_UNIXDATE;
-                       continue;
+       const struct lls_command *cmd = SERVER_CMD_CMD_PTR(LS);
+       struct ls_options *opts;
+       struct osl_object query;
+       const struct lls_opt_result *r_l = SERVER_CMD_OPT_RESULT(LS, LISTING_MODE,
+               lpr);
+       const struct lls_opt_result *r_s = SERVER_CMD_OPT_RESULT(LS, SORT, lpr);
+       int ret;
+       char *slpr;
+
+       ret = lls_serialize_parse_result(lpr, cmd, NULL, &query.size);
+       assert(ret >= 0);
+       query.size += sizeof(*opts);
+       query.data = para_malloc(query.size);
+       opts = query.data;
+       memset(opts, 0, sizeof(*opts));
+       slpr = query.data + sizeof(*opts);
+       ret = lls_serialize_parse_result(lpr, cmd, &slpr, NULL);
+       assert(ret >= 0);
+       opts->mode = LS_MODE_SHORT;
+       opts->sorting = LS_SORT_BY_PATH;
+       if (lls_opt_given(r_l)) {
+               const char *val = lls_string_val(0, r_l);
+               if (!strcmp(val, "l") || !strcmp(val, "long"))
+                       opts->mode = LS_MODE_LONG;
+               else if (!strcmp(val, "s") || !strcmp(val, "short"))
+                       opts->mode = LS_MODE_SHORT;
+               else if (!strcmp(val, "v") || !strcmp(val, "verbose"))
+                       opts->mode = LS_MODE_VERBOSE;
+               else if (!strcmp(val, "m") || !strcmp(val, "mbox"))
+                       opts->mode = LS_MODE_MBOX;
+               else if (!strcmp(val, "c") || !strcmp(val, "chunk-table"))
+                       opts->mode = LS_MODE_MBOX;
+               else if (!strcmp(val, "p") || !strcmp(val, "parser-friendly"))
+                       opts->mode = LS_MODE_PARSER;
+               else {
+                       ret = -E_AFT_SYNTAX;
+                       goto out;
                }
-               /* The compatibility remark above applies also to -s. */
-               if (!strncmp(arg, "-s", 2)) {
-                       arg += 2;
-                       if (*arg == '=')
-                               arg++;
-                       switch (*arg) {
-                       case 'p':
-                               sort = LS_SORT_BY_PATH;
-                               continue;
-                       case 's': /* -ss implies -a */
-                               sort = LS_SORT_BY_SCORE;
-                               flags |= LS_FLAG_ADMISSIBLE_ONLY;
-                               continue;
-                       case 'l':
-                               sort = LS_SORT_BY_LAST_PLAYED;
-                               continue;
-                       case 'n':
-                               sort = LS_SORT_BY_NUM_PLAYED;
-                               continue;
-                       case 'f':
-                               sort = LS_SORT_BY_FREQUENCY;
-                               continue;
-                       case 'c':
-                               sort = LS_SORT_BY_CHANNELS;
-                               continue;
-                       case 'i':
-                               sort = LS_SORT_BY_IMAGE_ID;
-                               continue;
-                       case 'y':
-                               sort = LS_SORT_BY_LYRICS_ID;
-                               continue;
-                       case 'b':
-                               sort = LS_SORT_BY_BITRATE;
-                               continue;
-                       case 'd':
-                               sort = LS_SORT_BY_DURATION;
-                               continue;
-                       case 'a':
-                               sort = LS_SORT_BY_AUDIO_FORMAT;
-                               continue;
-                       default:
-                               return -E_AFT_SYNTAX;
-                       }
+       }
+       if (lls_opt_given(r_s)) {
+               const char *val = lls_string_val(0, r_s);
+               if (!strcmp(val, "p") || !strcmp(val, "path"))
+                       opts->sorting = LS_SORT_BY_PATH;
+               else if (!strcmp(val, "s") || !strcmp(val, "score"))
+                       opts->sorting = LS_SORT_BY_SCORE;
+               else if (!strcmp(val, "l") || !strcmp(val, "lastplayed"))
+                       opts->sorting = LS_SORT_BY_LAST_PLAYED;
+               else if (!strcmp(val, "n") || !strcmp(val, "numplayed"))
+                       opts->sorting = LS_SORT_BY_NUM_PLAYED;
+               else if (!strcmp(val, "f") || !strcmp(val, "frquency"))
+                       opts->sorting = LS_SORT_BY_FREQUENCY;
+               else if (!strcmp(val, "c") || !strcmp(val, "channels"))
+                       opts->sorting = LS_SORT_BY_CHANNELS;
+               else if (!strcmp(val, "i") || !strcmp(val, "image-id"))
+                       opts->sorting = LS_SORT_BY_IMAGE_ID;
+               else if (!strcmp(val, "y") || !strcmp(val, "lyrics-id"))
+                       opts->sorting = LS_SORT_BY_LYRICS_ID;
+               else if (!strcmp(val, "b") || !strcmp(val, "bitrate"))
+                       opts->sorting = LS_SORT_BY_BITRATE;
+               else if (!strcmp(val, "d") || !strcmp(val, "duration"))
+                       opts->sorting = LS_SORT_BY_DURATION;
+               else if (!strcmp(val, &qu