Merge branch 'maint'
authorAndre Noll <maan@systemlinux.org>
Sun, 23 May 2010 06:34:56 +0000 (08:34 +0200)
committerAndre Noll <maan@systemlinux.org>
Sun, 23 May 2010 06:34:56 +0000 (08:34 +0200)
191 files changed:
.gitignore
COPYING.LIB [new file with mode: 0644]
CREDITS
Doxyfile
FEATURES
GIT-VERSION-GEN [new file with mode: 0755]
INSTALL
Makefile.in
NEWS
README
README.afs [deleted file]
REQUIREMENTS [deleted file]
aac.h
aac_afh.c
aac_common.c
aacdec_filter.c
acl.c
acl.h
afh.c
afh.h
afh_common.c
afs.c
afs.cmd
afs.h
aft.c
alsa_write.c
amp_filter.c
attribute.c
audioc.c
audiod.c
audiod.cmd
audiod.h
audiod_command.c
autogen.sh
bash_completion
bitstream.c [new file with mode: 0644]
bitstream.h [new file with mode: 0644]
blob.c
buffer_tree.c [new file with mode: 0644]
buffer_tree.h [new file with mode: 0644]
chunk_queue.c
chunk_queue.h
client.c
client.h
client_common.c
close_on_fork.c
command.c
command.h [new file with mode: 0644]
command_util.sh
compress_filter.c
configure.ac
convert_0.2-0.3.sh [deleted file]
convert_0.3-0.4.sh [new file with mode: 0755]
crypt.c
crypt.h
daemon.c
daemon.h
dccp_recv.c
dccp_send.c
depend.sh [new file with mode: 0755]
error.h
exec.c
fade.c
fd.c
fd.h
fec.c
fec.h
fecdec_filter.c
file_write.c
filter.c
filter.h
filter_common.c
fsck.c [deleted file]
gcc-compat.h
ggo.c
ggo.h
ggo/.gitignore
ggo/afh.m4
ggo/alsa_write.ggo [deleted file]
ggo/alsa_write.m4 [new file with mode: 0644]
ggo/audiod.m4
ggo/client.m4
ggo/dccp_recv.ggo
ggo/fade.ggo
ggo/grab_client.ggo [deleted file]
ggo/gui.m4
ggo/http_recv.ggo [deleted file]
ggo/http_recv.m4 [new file with mode: 0644]
ggo/log_timing.m4 [new file with mode: 0644]
ggo/makefile
ggo/mp3dec_filter.ggo [deleted file]
ggo/mp3dec_filter.m4 [new file with mode: 0644]
ggo/osx_write.ggo
ggo/server.m4
ggo/write.m4
grab_client.c
grab_client.h
gui.c
gui.h
gui_theme.c
hash.h
http_recv.c
http_send.c
imdct.c [new file with mode: 0644]
imdct.h [new file with mode: 0644]
ipc.c
list.h
mm.c [new file with mode: 0644]
mm.h [new file with mode: 0644]
mood.c
mood.h [new file with mode: 0644]
mp3_afh.c
mp3dec_filter.c
net.c
net.h
ogg_afh.c
oggdec_filter.c
osl.c [deleted file]
osl.h [deleted file]
osl_core.h [deleted file]
oss_write.c
osx_write.c
para.h
pics/paraslash/default.jpg [deleted file]
pics/screenshots/audiod.log [deleted file]
pics/screenshots/gui.png [deleted file]
pics/screenshots/server.log [deleted file]
pics/web/paraslash.ico [deleted file]
pics/web/paraslash.png [deleted file]
playlist.c
portable_io.h
prebuffer_filter.c
rbtree.c [deleted file]
rbtree.h [deleted file]
rc4.h
recv.c
recv.h
recv_common.c
ringbuffer.c
ringbuffer.h
sched.c
sched.h
score.c
send.h
send_common.c
server.c
server.cmd
server.h
sha1.c
signal.c
signal.h
skencil/overview.sk
stat.c
stdin.c
stdin.h
stdout.c
stdout.h
string.c
string.h
time.c
udp_recv.c
udp_send.c
user_list.c
user_list.h
versions/paraslash-0.4.0.tar.bz2 [new file with mode: 0644]
versions/paraslash-0.4.0.tar.bz2.asc [new file with mode: 0644]
versions/paraslash-0.4.1.tar.bz2 [new file with mode: 0644]
versions/paraslash-0.4.1.tar.bz2.asc [new file with mode: 0644]
versions/paraslash-0.4.2.tar.bz2 [new file with mode: 0644]
versions/paraslash-0.4.2.tar.bz2.asc [new file with mode: 0644]
vss.c
vss.h
wav_filter.c
web/documentation.in.html
web/images/paraslash.ico [new file with mode: 0644]
web/images/paraslash.png [new file with mode: 0644]
web/index.in.html
web/manual.m4 [new file with mode: 0644]
web/para.css
web/screenshots/audiod.log [new file with mode: 0644]
web/screenshots/gui.png [new file with mode: 0644]
web/screenshots/server.log [new file with mode: 0644]
wma.h [new file with mode: 0644]
wma_afh.c [new file with mode: 0644]
wma_common.c [new file with mode: 0644]
wmadata.h [new file with mode: 0644]
wmadec_filter.c [new file with mode: 0644]
write.c
write.h
write_common.c
write_common.h

index 0000ba4..d3a1fc5 100644 (file)
@@ -1,4 +1,4 @@
-*.[oa]
+objects
 foo*
 bar*
 para_*
@@ -30,4 +30,4 @@ web_sync
 confdefs.h
 conftest
 conftest.c
-
+GIT-VERSION-FILE
diff --git a/COPYING.LIB b/COPYING.LIB
new file mode 100644 (file)
index 0000000..cf9b6b9
--- /dev/null
@@ -0,0 +1,510 @@
+
+                  GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations
+below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+^L
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it
+becomes a de-facto standard.  To achieve this, non-free programs must
+be allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+^L
+                  GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control
+compilation and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+\f
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+^L
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+^L
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at least
+    three years, to give the same user the materials specified in
+    Subsection 6a, above, for a charge no more than the cost of
+    performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+^L
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+^L
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply, and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License
+may add an explicit geographical distribution limitation excluding those
+countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+^L
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+                            NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+^L
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms
+of the ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.
+It is safest to attach them to the start of each source file to most
+effectively convey the exclusion of warranty; and each file should
+have at least the "copyright" line and a pointer to where the full
+notice is found.
+
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or
+your school, if any, to sign a "copyright disclaimer" for the library,
+if necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James
+  Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/CREDITS b/CREDITS
index e5f12ac..15313f9 100644 (file)
--- a/CREDITS
+++ b/CREDITS
@@ -14,6 +14,8 @@ Karl Bartel <karlb@gmx.net> (SFont)
 
 Gerd Becker <gerd.mac@gmx.net> (MacOs testing)
 
+Fabrice Bellard (FFmpeg)
+
 Lorenzo Bettini <bettini@dsi.unifi.it> (gengetopt)
 
 Ricardo Cerqueira <rmc@rccn.net> (mp3info)
@@ -34,12 +36,16 @@ Robert Leslie (libmad)
 
 Ian McDonald <imcdnzl@gmail.com> (dccp example code)
 
+Loren Merritt (FFmpeg)
+
 Simon Morlat <simon.morlat@linphone.org> (ortp)
 
 Christof Müller (bug reports)
 
 M. Hari Nezumi <magenta@trikuare.cx> (AudioCompress)
 
+Michael Niedermayer <michaelni@gmx.at> (FFmpeg)
+
 Manuel Odendahl <manuel-poc@bl0rg.net> (poc)
 
 Guillaume Outters <guillaume.outters@free.fr> (mosx-mpg123)
index 43209a5..6b5cb4a 100644 (file)
--- a/Doxyfile
+++ b/Doxyfile
@@ -255,7 +255,7 @@ EXTRACT_STATIC         = NO
 # defined locally in source files will be included in the documentation. 
 # If set to NO only classes defined in header files are included.
 
-EXTRACT_LOCAL_CLASSES  = YES
+EXTRACT_LOCAL_CLASSES  = NO
 
 # This flag is only useful for Objective-C code. When set to YES local 
 # methods, which are defined in the implementation section but not in 
@@ -1063,7 +1063,8 @@ PREDEFINED             = HAVE_MAD \
                         HAVE_FAAD \
                          HAVE_OGGVORBIS \
                          __GNUC__=4 \
-                         __GNUC_MINOR__=4
+                         __GNUC_MINOR__=4 \
+                         HAVE_UCRED
 
 # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then 
 # this tag can be used to specify a list of macro names that should be expanded. 
index d054dfd..dc7f853 100644 (file)
--- a/FEATURES
+++ b/FEATURES
@@ -5,7 +5,7 @@ Features
 
        * Runs on Linux, Mac OS, FreeBSD, NetBSD, Solaris and probably other
          Unixes
-       * Mp3, ogg vorbis, aac(m4a) support
+       * Mp3, ogg vorbis, aac (m4a) and wma support
        * Local or remote http, dccp, and udp network audio streaming
        * IPv6 support
        * Forward error correction allows receivers to recover from packet losses
diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN
new file mode 100755 (executable)
index 0000000..a64d796
--- /dev/null
@@ -0,0 +1,45 @@
+#!/bin/sh
+
+if test $# -ne 1; then
+       echo >&2 "usage: $0 <version file>"
+       exit 1
+fi
+
+GVF="$1"
+DEF_VER="unnamed_version"
+
+LF='
+'
+
+# First see if there is a version file (included in release tarballs),
+# then try git-describe, then default.
+if test -f VERSION
+then
+       VN=$(cat VERSION) || VN="$DEF_VER"
+elif test -d .git -o -f .git &&
+       VN=$(git describe --abbrev=4 HEAD 2>/dev/null) &&
+       case "$VN" in
+       *$LF*) (exit 1) ;;
+       v[0-9]*)
+               git update-index -q --refresh
+               test -z "$(git diff-index --name-only HEAD --)" ||
+               VN="$VN-dirty" ;;
+       esac
+then
+       VN=$(echo "$VN" | sed -e 's/-/./g');
+else
+       VN="$DEF_VER"
+fi
+
+VN=$(expr "$VN" : v*'\(.*\)')
+
+if test -r $GVF
+then
+       VC=$(sed -e 's/^GIT_VERSION = //' <$GVF)
+else
+       VC=unset
+fi
+test "$VN" = "$VC" || {
+       echo >&2 "GIT_VERSION = $VN"
+       echo "GIT_VERSION = $VN" >$GVF
+}
diff --git a/INSTALL b/INSTALL
index 09a2616..0f4c18b 100644 (file)
--- a/INSTALL
+++ b/INSTALL
-INSTALL
-=======
-
-----
 Any knowledge of how to work with mouse and icons is not required.
 
----------------------------
-Install all needed packages
----------------------------
-See
-<<
-<a href="REQUIREMENTS.html"> REQUIREMENTS </a>
->>
-for a list of required software. You don't need everything listed
-there. In particular, mp3, ogg vorbis and aac support are all
-optional. The configure script will detect what is installed on your
-system and will only try to build those executables that can be built
-with your setup.
-
-Note that no special library (not even the mp3 decoding library libmad)
-is needed for para_server if you only want to stream mp3 files.
-Also, it's fine to use para_server on a box without sound card as
-para_server only sends the audio stream to connected clients.
-
--------------------------
-Install server and client
--------------------------
-
-Install the package on all machines, you'd like this software to run on:
-
-       (./configure && make) > /dev/null
-
-There should be no errors but probably some warnings about missing
-software packages which usually implies that not all audio formats will
-be supported. If headers or libs are installed at unusual locations
-you might need to tell the configure script where to find them. Try
-
-       ./configure --help
-
-to see a list of options. If the paraslash package was compiled
-successfully, execute as root,
-
-       make install
-
------------------------------------
-Setup user list and create rsa keys
------------------------------------
-
-If you already have your rsa keys, skip this step. If you are new
-to paraslash, you have to generate an rsa key pair for each user you
-want to allow to connect. You need at least one user.
-
-Let's assume that you'd like to run the server on host server_host
-as user foo, and that you want to connect from client_host as user bar.
-
-As foo@server_host, create ~/.paraslash/server.users by typing the
-following commands:
-
-       user=bar
-       target=~/.paraslash/server.users
-       key=~/.paraslash/key.pub.$user
-       perms=AFS_READ,AFS_WRITE,VSS_READ,VSS_WRITE
-       mkdir -p ~/.paraslash
-       echo "user $user $key $perms" >> $target
-
-This gives "bar" the full privileges.
-
-Change to the "bar" account on client_host and generate the key-pair
-with the commands
-
-       key=~/.paraslash/key.$LOGNAME
-       mkdir -p ~/.paraslash
-       (umask 077 && openssl genrsa -out $key)
-
-Next, extract its public part:
-
-       pubkey=~/.paraslash/key.pub.$LOGNAME
-       openssl rsa -in $key -pubout -out $pubkey
-
-and copy the public key just created to server_host (you may
-skip this step for a single-user setup, i.e. if foo=bar and
-server_host=client_host):
-
-       scp $pubkey foo@server_host:.paraslash/
-
-Finally, tell para_client to connect to server_host:
-
-       conf=~/.paraslash/client.conf
-       echo 'hostname server_host' > $conf
-
------------------
-Start para_server
------------------
-
-Before starting the server make sure you have write permissions to
-the directory /var/paraslash.
-
-       sudo chown $LOGNAME /var/paraslash
-
-Alternatively, use the --afs_socket Option to specify a different
-location for the afs command socket.
-
-For this first try, we'll use the info loglevel to make the output
-of para_server more verbose.
-
-       para_server -l info
-
-Now you can use para_client to connect to the server and issue
-commands. Open a new shell (as "bar" on "client_host" in the above
-example) and try
-
-       para_client help
-       para_client si
-
-to retrieve the list of available commands and some server info.
-Don't proceed if this doesn't work.
-
--------------------
-Create the database
--------------------
-
-       para_client init
-
-This creates some empty tables under ~/.paraslash/afs_database.
-You normally don't need to look at these tables, but it's good
-to know that you can start from scratch with
-
-       rm -rf ~/.paraslash/afs_database
-
-in case something went wrong.
-
-Next, you need to fill the audio file table of that database with
-contents so that para_server knows about your audio files.  Choose an
-absolute path to a directory containing some audio files and add them
-to the audio file table:
-
-       para_client add /my/mp3/dir
-
-This might take a while, so it is a good idea to start with a directory
-containing not too many audio files. Note that the table only contains
-data about the audio files found, not the files themselves.
-
-Print a list of all audio files found with
-
-       para_client ls
-
-------------------------
-Start streaming manually
-------------------------
-
-       para_client play
-       para_client stat 2
-
-This starts streaming and dumps some information about the current
-audio file to stdout.
-
-You should now be able to receive the stream and listen to it. If
-you have mpg123 or xmms handy, execute on client_host
-
-       mpg123 http://server_host:8000/
-or
-       xmms http://server_host:8000/
-
-Paraslash comes with its own receiving and playing software, which
-will be described next. Try the following on client_host (assuming
-Linux/ALSA and an mp3 stream):
-
-       para_recv -l info -r 'http -i server_host' > file.mp3
-       # (interrupt with CTRL+C after a few seconds)
-       ls -l file.mp3 # should not be empty
-       para_filter -f mp3dec -f wav < file.mp3 > file.wav
-       ls -l file.wav # should be much bigger than file.mp3
-       para_write -w alsa < file.wav
-
-If this works, proceed. Otherwise double check what is logged by
-para_server and use the --loglevel option of para_recv, para_filter
-and para_write to increase verbosity.
-
-Next, put the pieces together:
-
-       para_recv -r 'http -i server_host' \
-               | para_filter -f mp3dec -f wav \
-               | para_write -w alsa
-
----------------------
-Configure para_audiod
----------------------
-
-In order to automatically start the right decoder at the right time
-and to offer to the clients some information on the current audio
-stream and on paraslash's internal state, you should run the local
-audio daemon, para_audiod, on every machine in your network which is
-supposed to play the audio stream. Try
-
-       para_audiod -h
-
-for help. Usually you have to specify only server_host as the receiver
-specifier for each supported audio format, like this:
-
-       para_audiod -l info -r 'mp3:http -i server_host'
-
-The preferred way to use para_audiod is to run it once at system start
-as an unprivileged user. para_audiod needs to create a "well-known"
-socket for the clients to connect to. The default path for this
-socket is
-
-       /var/paraslash/audiod_socket.$HOSTNAME
-
-so the /var/paraslash directory should be writable for the user who
-runs para_audiod.
-
-If you want to change the location of the socket, use the --socket
-option for para_audiod or the config file ~/.paraslash/audiod.conf
-to change the default. Note that in this case you'll also have to
-specify the same value for para_audioc's --socket option.
-
-If para_server is playing, you should be able to listen to the audio
-stream as soon as para_audiod is started.  Once it is running, try
-
-       para_audioc stat
-
-That should dump some information to stdout. Other commands include
+From tarball:
 
-       para_audioc off
-       para_audioc on
-       para_audioc sb
-       para_audioc term
-       para_audioc cycle
+       ./configure && make && sudo make install
 
---------------
-Start para_gui
---------------
+From git:
 
-para_gui reads the output of "para_audioc stat" and displays that
-information in a curses window. It also allows you to bind keys to
-arbitrary commands. There are several flavours of key-bindings:
+       ./autogen.sh && sudo make install
 
-       - internal: These are the built-in commands that can not be
-         changed (help, quit, loglevel, version...).
-       - external: Shutdown curses before launching the given command.
-         Useful for starting other ncurses programs from within
-         para_gui, e.g.  aumix or dialog scripts. Or, use the mbox
-         output format to write a mailbox containing one mail for each
-         (admissible) file the audio file selector knows about. Then
-         start mutt from within para_gui to browse your collection!
-       - display: Launch the command and display its stdout in
-         para_gui's bottom window.
-       - para: Like display, but start "para_client <specified
-         command>" instead of "<specified command>".
+For details see the user manual:
 
-This concludes the installation notes. Next thing you might to have a look
-at is how to use paraslash's audio file selector. See
-<<
-<a href="README.afs.html"> README.afs</a>
->>
+       http://paraslash.systemlinux.org/manual.html
index d04d0ea..34992de 100644 (file)
@@ -8,6 +8,7 @@ MANDIR := @datarootdir@/man/man1
 PACKAGE_VERSION := @PACKAGE_VERSION@
 PACKAGE_STRING := @PACKAGE_STRING@
 install_sh := @install_sh@
+cmdline_dir := @cmdline_dir@
 
 build_date := $(shell date)
 uname_s := $(shell uname -s 2>/dev/null || echo "UNKNOWN_OS")
@@ -55,151 +56,237 @@ CPPFLAGS += -DMAIN_INPUT_FILE_IS_$(*F)
 CPPFLAGS += @SSL_CPPFLAGS@
 CPPFLAGS += @ncurses_cppflags@
 CPPFLAGS += @arch_cppflags@
+CPPFLAGS += -I/usr/local/include
+CPPFLAGS += -I$(cmdline_dir)
+CPPFLAGS += @osl_cppflags@
+CPPFLAGS += -DGIT_VERSION='"$(GIT_VERSION)"'
 
-BINARIES = para_server para_client para_audioc para_recv \
-       para_filter para_write para_fsck para_afh @extra_binaries@
-man_binaries := $(BINARIES)
-man_pages := $(patsubst %, man/man1/%.1, $(man_binaries))
-man_pages_in := $(patsubst %, web/%.man.in.html, $(man_binaries))
+man_pages := $(patsubst %, man/man1/%.1, @executables@)
+man_pages_in := $(patsubst %, web/%.man.in.html, @executables@)
 
 ggo_dir := ggo
+object_dir := objects
+man_dir := man/man1
 
-m4_ggos := afh audioc audiod client filter fsck gui recv server write
+m4_ggos := afh audioc audiod client filter gui recv server write
 all_ggos := $(m4_ggos) dccp_recv oggdec_filter alsa_write oss_write fade http_recv \
        osx_write udp_recv amp_filter compress_filter file_write \
-       grab_client mp3dec_filter
-ggo_generated := $(addsuffix .cmdline.c, $(all_ggos)) $(addsuffix .cmdline.h, $(all_ggos)) \
-       $(addsuffix .ggo, $(addprefix $(ggo_dir)/,$(m4_ggos)))
+       mp3dec_filter prebuffer_filter
+ggo_generated := $(addsuffix .ggo, $(addprefix $(ggo_dir)/,$(m4_ggos)))
+cmdline_generated := $(addprefix $(cmdline_dir)/,$(addsuffix .cmdline.c, $(all_ggos)) \
+       $(addsuffix .cmdline.h, $(all_ggos)))
 
 autocrap := config.h.in configure
 tarball_pfx := @PACKAGE_TARNAME@-$(PACKAGE_VERSION)
-tarball_delete = web versions pics .changelog_before_cvs .changelog_cvs .gitignore
-tarball_delete := $(patsubst %,$(tarball_pfx)/%,$(tarball_delete))
-tarball_add := $(ggo_generated) $(autocrap)
+tarball_delete := $(addprefix $(tarball_pfx)/,\
+       web versions .changelog_before_cvs .changelog_cvs .gitignore\
+       $(ggo_dir) skencil)
 tarball := @PACKAGE_TARNAME@-$(PACKAGE_VERSION).tar.bz2
 
-.PHONY: clean distclean maintainer-clean install man tarball
-all: $(BINARIES) $(man_pages)
+# To put more focus on warnings, be less verbose as default
+# Use 'make V=1' to see the full commands
+ifdef V
+       ifeq ("$(origin V)", "command line")
+               BUILD_VERBOSE = $(V)
+       endif
+endif
+ifndef BUILD_VERBOSE
+       BUILD_VERBOSE = 0
+endif
+ifeq ($(BUILD_VERBOSE),1)
+       Q =
+else
+       Q = @
+endif
+
+.PHONY: all clean distclean maintainer-clean install man tarball\
+       .FORCE-GIT-VERSION-FILE
+all: @executables@ $(man_pages)
 man: $(man_pages)
 tarball: $(tarball)
 
-*.o: para.h config.h gcc-compat.h
+GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
+       @./GIT-VERSION-GEN GIT-VERSION-FILE
+-include GIT-VERSION-FILE
+@executables@: GIT-VERSION-FILE
 
-include Makefile.deps
-include $(ggo_dir)/makefile
-
-%_command_list.c %_command_list.h: %.cmd
-       ./command_util.sh c < $< >$@
-       ./command_util.sh h < $< >$(@:%.c=%.h)
+-include $(ggo_dir)/makefile
 
+%_command_list.c: %.cmd
+       @[ -z "$(Q)" ] || echo 'GEN $@'
+       $(Q) ./command_util.sh c < $< >$@
+%_command_list.h: %.cmd
+       @[ -z "$(Q)" ] || echo 'GEN $@'
+       $(Q) ./command_util.sh h < $< >$@
 %_command_list.man: %.cmd
-       ./command_util.sh man < $< > $@
-
-server_command_lists = server_command_list.man afs_command_list.man
-man/man1/para_server.1: para_server $(server_command_lists)
-       mkdir -p man/man1
-       opts="-h --detailed-help -N `for i in $(server_command_lists); do printf "%s\n" "-i $$i"; done`"; \
+       @[ -z "$(Q)" ] || echo 'GEN $@'
+       $(Q) ./command_util.sh man < $< > $@
+
+server_command_lists_ch = server_command_list.c afs_command_list.c \
+       server_command_list.h afs_command_list.h
+server_command_lists_man = server_command_list.man afs_command_list.man
+man/man1/para_server.1: para_server $(server_command_lists_man) | $(man_dir)
+       @[ -z "$(Q)" ] || echo 'HELP2MAN $<'
+       $(Q) opts="-h --detailed-help -N `for i in $(server_command_lists_man); do printf "%s\n" "-i $$i"; done`"; \
        help2man $$opts ./para_server > $@
 
-man/man1/para_audiod.1: para_audiod audiod_command_list.man
-       mkdir -p man/man1
-       help2man -h --detailed-help -N -i audiod_command_list.man ./para_audiod > $@
+man/man1/para_audiod.1: para_audiod audiod_command_list.man | $(man_dir)
+       @[ -z "$(Q)" ] || echo 'HELP2MAN $<'
+       $(Q) help2man -h --detailed-help -N -i audiod_command_list.man ./para_audiod > $@
 
-man/man1/%.1: %
-       mkdir -p man/man1
-       help2man -h --detailed-help -N ./$< > $@
+man/man1/%.1: % | $(man_dir)
+       @[ -z "$(Q)" ] || echo 'HELP2MAN $<'
+       $(Q) help2man -h --detailed-help -N ./$< > $@
 
 man/html/%.html: man/man1/%.1
-       mkdir -p man/html
-       man2html $< > $@
+       @[ -z "$(Q)" ] || echo 'MAN2HTML $<'
+       $(Q) mkdir -p man/html
+       $(Q) man2html $< > $@
 
 web/%.man.in.html: man/man1/%.1
-       man2html $< | sed -e '/^<\/BODY>/,$$d' -e '1,/<\/HEAD><BODY>/d' > $@
-
-
-oggdec_filter.o: oggdec_filter.c
-       $(CC) -c $(CPPFLAGS) $(DEBUG_CPPFLAGS) @oggvorbis_cppflags@ $<
-ogg_afh.o: ogg_afh.c
-       $(CC) -c $(CPPFLAGS) $(DEBUG_CPPFLAGS) @oggvorbis_cppflags@ $<
-
-mp3dec_filter.o: mp3dec_filter.c
-       $(CC) -c $(CPPFLAGS) $(DEBUG_CPPFLAGS) @mad_cppflags@ $<
-
-aacdec_filter.o: aacdec_filter.c
-       $(CC) -c $(CPPFLAGS) $(DEBUG_CPPFLAGS) @faad_cppflags@ $<
-
-aac_common.o: aac_common.c
-       $(CC) -c $(CPPFLAGS) $(DEBUG_CPPFLAGS) @faad_cppflags@ $<
-
-aac_afh.o: aac_afh.c
-       $(CC) -c $(CPPFLAGS) $(DEBUG_CPPFLAGS) @faad_cppflags@ $<
-
-%.cmdline.o: %.cmdline.c
-       $(CC) -c $(CPPFLAGS) $<
+       @[ -z "$(Q)" ] || echo 'MAN2HTML $<'
+       $(Q) mkdir -p man/html
+       $(Q) man2html $< | sed -e '/^<\/BODY>/,$$d' -e '1,/<\/HEAD><BODY>/d' > $@
+
+$(object_dir):
+       mkdir -p $@
+$(man_dir):
+       mkdir -p $@
+
+$(object_dir)/oggdec_filter.o: oggdec_filter.c | $(object_dir)
+       @[ -z "$(Q)" ] || echo 'CC $<'
+       $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @oggvorbis_cppflags@ $<
+
+$(object_dir)/ogg_afh.o: ogg_afh.c | $(object_dir)
+       @[ -z "$(Q)" ] || echo 'CC $<'
+       $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @oggvorbis_cppflags@ $<
+
+$(object_dir)/mp3dec_filter.o: mp3dec_filter.c | $(object_dir)
+       @[ -z "$(Q)" ] || echo 'CC $<'
+       $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @mad_cppflags@ $<
+
+$(object_dir)/aacdec_filter.o: aacdec_filter.c | $(object_dir)
+       @[ -z "$(Q)" ] || echo 'CC $<'
+       $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @faad_cppflags@ $<
+
+$(object_dir)/aac_common.o: aac_common.c | $(object_dir)
+       @[ -z "$(Q)" ] || echo 'CC $<'
+       $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @faad_cppflags@ $<
+
+$(object_dir)/aac_afh.o: aac_afh.c | $(object_dir)
+       @[ -z "$(Q)" ] || echo 'CC $<'
+       $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @faad_cppflags@ $<
+
+$(object_dir)/%.cmdline.o: $(cmdline_dir)/%.cmdline.c $(cmdline_dir)/%.cmdline.h | $(object_dir)
+       @[ -z "$(Q)" ] || echo 'CC $<'
+       $(Q) $(CC) -c $(CPPFLAGS) -o $@ $<
+
+$(object_dir)/%.o: %.c | $(object_dir)
+       @[ -z "$(Q)" ] || echo 'CC $<'
+       $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) $<
+
+$(object_dir)/%.cmdline.d: $(cmdline_dir)/%.cmdline.c | $(object_dir)
+       @[ -z "$(Q)" ] || echo 'DEP $<'
+       $(Q) ./depend.sh $(object_dir) $(cmdline_dir) $(CPPFLAGS) $< > $@
+
+$(object_dir)/%.d: %.c | $(object_dir)
+       @[ -z "$(Q)" ] || echo 'DEP $<'
+       $(Q) ./depend.sh $(object_dir) $(cmdline_dir) $(CPPFLAGS) $< > $@
+
+recv_objs := $(addprefix $(object_dir)/, @recv_objs@)
+filter_objs := $(addprefix $(object_dir)/, @filter_objs@)
+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@)
+server_objs := $(addprefix $(object_dir)/, @server_objs@)
+write_objs := $(addprefix $(object_dir)/, @write_objs@)
+afh_objs := $(addprefix $(object_dir)/, @afh_objs@)
+
+all_objs := $(recv_objs) $(filter_objs) $(client_objs) $(gui_objs) \
+       $(audiod_objs) $(audioc_objs) $(fade_objs) $(server_objs) \
+       $(write_objs) $(afh_objs)
+
+ifeq ($(findstring clean, $(MAKECMDGOALS)),)
+-include $(all_objs:.o=.d)
+endif
 
-%.o: %.c
-       $(CC) -c $(CPPFLAGS) $(DEBUG_CPPFLAGS) $<
+para_recv: $(recv_objs)
+       @[ -z "$(Q)" ] || echo 'LD $@'
+       $(Q) $(CC) $(LDFLAGS) $(recv_objs) -o $@ @recv_ldflags@
 
-para_recv: @recv_objs@
-       $(CC) $(LDFLAGS) @recv_objs@ -o $@ @recv_ldflags@
+para_filter: $(filter_objs)
+       @[ -z "$(Q)" ] || echo 'LD $@'
+       $(Q) $(CC) $(LDFLAGS) $(filter_objs) -o $@ @filter_ldflags@
 
-para_filter: @filter_objs@
-       $(CC) $(LDFLAGS) @filter_objs@ -o $@ @filter_ldflags@
+para_client: $(client_objs)
+       @[ -z "$(Q)" ] || echo 'LD $@'
+       $(Q) $(CC) $(LDFLAGS) -o $@ $(client_objs) @client_ldflags@
 
-para_client: @client_objs@
-       $(CC) $(LDFLAGS) -o $@ @client_objs@ @client_ldflags@
+para_gui: $(gui_objs)
+       @[ -z "$(Q)" ] || echo 'LD $@'
+       $(Q) $(CC) $(LDFLAGS) -o $@ $(gui_objs) -lncurses
 
-para_gui: @gui_objs@
-       $(CC) $(LDFLAGS) -o $@ @gui_objs@ -lncurses
+para_audiod: audiod_command_list.c audiod_command_list.h $(audiod_objs)
+       @[ -z "$(Q)" ] || echo 'LD $@'
+       $(Q) $(CC) $(LDFLAGS) -o $@ $(audiod_objs) @audiod_ldflags@
 
-para_audiod: @audiod_objs@
-       $(CC) $(LDFLAGS) -o $@ @audiod_objs@ @audiod_ldflags@
+para_audioc: $(audioc_objs)
+       @[ -z "$(Q)" ] || echo 'LD $@'
+       $(Q) $(CC) $(LDFLAGS) -o $@ $(audioc_objs) @audioc_ldflags@
 
-para_audioc: @audioc_objs@
-       $(CC) $(LDFLAGS) -o $@ @audioc_objs@ @audioc_ldflags@
+para_fade: $(fade_objs)
+       @[ -z "$(Q)" ] || echo 'LD $@'
+       $(Q) $(CC) $(LDFLAGS) -o $@ $(fade_objs) @fade_ldflags@
 
-para_fade: @fade_objs@
-       $(CC) $(LDFLAGS) -o $@ @fade_objs@ @fade_ldflags@
+para_server: $(server_command_lists_ch) $(server_objs)
+       @[ -z "$(Q)" ] || echo 'LD $@'
+       $(Q) $(CC) $(LDFLAGS) -o $@ $(server_objs)  @server_ldflags@
 
-para_server: @server_objs@
-       $(CC) $(LDFLAGS) -o $@ @server_objs@  @server_ldflags@
+para_write: $(write_objs)
+       @[ -z "$(Q)" ] || echo 'LD $@'
+       $(Q) $(CC) $(LDFLAGS) -o $@ $(write_objs) @write_ldflags@
 
-para_fsck: @fsck_objs@
-       $(CC) $(LDFLAGS) -o $@ @fsck_objs@ @fsck_ldflags@
+para_afh: $(afh_objs)
+       @[ -z "$(Q)" ] || echo 'LD $@'
+       $(Q) $(CC) $(LDFLAGS) -o $@ $(afh_objs) @afh_ldflags@
 
-para_write: @write_objs@
-       $(CC) $(LDFLAGS) -o $@ @write_objs@ @write_ldflags@
+clean:
+       @[ -z "$(Q)" ] || echo 'CLEAN'
+       $(Q) rm -f @executables@ $(object_dir)/*.o
 
-para_afh: @afh_objs@
-       $(CC) $(LDFLAGS) -o $@ @afh_objs@ @afh_ldflags@
+clean2: clean
+       @[ -z "$(Q)" ] || echo 'CLEAN2'
+       $(Q) rm -rf man $(object_dir)
+       $(Q) rm -f *_command_list.*
 
-clean:
-       rm -f *.o $(BINARIES)
-       rm -rf man
-distclean: clean
-       rm -f Makefile autoscan.log config.status config.log && \
-       rm -rf web/sync/* autom4te.cache aclocal.m4
-       rm -f GPATH GRTAGS GSYMS GTAGS
+distclean: clean2
+       @[ -z "$(Q)" ] || echo 'DISTCLEAN'
+       $(Q) rm -f Makefile autoscan.log config.status config.log
+       $(Q) rm -rf autom4te.cache aclocal.m4
+       $(Q) rm -f GPATH GRTAGS GSYMS GTAGS
 
 maintainer-clean: distclean
        rm -f $(ggo_generated) *.tar.bz2 \
                config.h configure \
                config.h.in skencil/*.pdf skencil/*.ps
-       rm -f *_command_list.* *.man man/man1/*
-       rm -rf web_sync
+       rm -rf web_sync $(cmdline_dir)
 
 install: all man
        mkdir -p $(BINDIR) $(MANDIR)
-       $(install_sh) -s -m 755 $(BINARIES) $(BINDIR)
+       $(install_sh) -s -m 755 @executables@ $(BINDIR)
        $(install_sh) -m 644 $(man_pages) $(MANDIR)
        mkdir -p $(VARDIR) >/dev/null 2>&1 || true # not fatal, so don't complain
 
-@PACKAGE_TARNAME@-$(PACKAGE_VERSION).tar.bz2: $(tarball_add)
+$(tarball): $(cmdline_generated)
        rm -rf $(tarball_pfx).tar.bz2 $(tarball_pfx)
        git archive --format=tar --prefix=$(tarball_pfx)/ HEAD \
                | tar --delete $(tarball_delete) > $(tarball_pfx).tar
-       mkdir $(tarball_pfx)
-       cp -r $(tarball_add) $(tarball_pfx)
+       mkdir -p $(tarball_pfx)/$(cmdline_dir)
+       echo $(GIT_VERSION) > $(tarball_pfx)/VERSION
+       cp -r $(autocrap) $(tarball_pfx)
+       cp -r $(cmdline_generated) $(tarball_pfx)/$(cmdline_dir)
        tar rf $(tarball_pfx).tar $(tarball_pfx)/*
        rm -rf $(tarball_pfx)
        bzip2 -9 $(tarball_pfx).tar
diff --git a/NEWS b/NEWS
index 66ce1f3..c17a543 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,48 @@
 NEWS
 ====
 
+---------------------------------------------
+0.4.3 (to be announced) "imaginary radiation"
+---------------------------------------------
+
+       - Fix an end-of-file detection bug in the oggdec filter.
+       - New user manual
+       - The new nonblock API
+
+------------------------------------------
+0.4.2 (2010-04-23) "associative expansion"
+------------------------------------------
+
+It's been some time since the last release, but finally here is
+paraslash-0.4.2. The bulk of the changes comes from the new buffer
+tree API, but there are changes all over the tree. Mainly performance
+and usability improvements, but also quite some bug fixes.
+
+       - The new buffer tree API.
+       - DCCP: Support for CCID negotiation (Gerrit Renker).
+       - UDP robustness fixes.
+       - The --bufsize option for mp3dec is gone as it no longer makes sense
+         for the new buffer tree API.
+       - Fix audible buffer underruns for wma streams.
+       - The alsa writer no longer prints meaningless underrun durations.
+       - audiod: Defaults work also for udp streams. If no filter is
+         given for an audio format that is received via upd, fecdec is
+         automatically added as the first filter (along with the decoder).
+
+---------------------------------------
+0.4.1 (2009-12-22) "concurrent horizon"
+---------------------------------------
+
+Support for another audio format, minor feature enhancements and lots of bug
+fixes. All fixes that have been accumulated in the maint branch (in particular
+those mentionened in the 0.3.6 release notes) appear in this release as well.
+
+       - wma support.
+       - new afh option: --human to activate human-readable output.
+       - new server/audiod option: --log-timing to print timing information.
+       - build system improvements.
+       - source code documentation updates.
+
 -------------------------------------
 0.3.6 (2009-12-07) "cubic continuity"
 -------------------------------------
@@ -19,6 +61,28 @@ another 0.3.x release. No new features.
        - http/dccp: Do not send the audio file header twice.
        - FEC: Timing improvements.
 
+----------------------------------------------
+0.4.0 (2009-11-10) "simultaneous independence"
+----------------------------------------------
+
+Two significant changes which require the new version number: The
+improved authentication dialog and the fact that the database code
+has been moved to a library, libosl. To use the new version, you have
+to generate new RSA keys, see INSTALL for details. A shell script is
+provided for conversion of the 0.3 database to the new 0.4 format.
+
+       - stronger crypto for client authentication
+       - the database code has been moved to a library
+       - improved status item handling
+       - cleanup of the build system
+       - The "-V" option now also prints the git version
+       - the new parser-friendly listing mode for the ls and stat commands
+       - mandatory rc4 encryption
+       - major audio format handler cleanups
+       - (id3,...) tags are no longer stored as a combined string in the database
+       - new mood methods: artist_matches, title_matches, comment_matches,
+         album_matches, year_maches, year.
+
 --------------------------------------------
 0.3.5 (2009-09-21) "symplectic separability"
 --------------------------------------------
diff --git a/README b/README
index 6cf34f2..e991b73 100644 (file)
--- a/README
+++ b/README
@@ -6,166 +6,10 @@ Paraslash is an acronym for
 
 _Play, archive, rate and stream large audio sets happily_
 
-It contains the following programs:
+The paraslash package contains server and client software for
+network streaming as well as stand-alone utilities for decoding
+mp3, ogg, aac and wma files. See the user manual for details.
 
------------
-para_server
------------
-
-para_server streams binary audio data (mp3/oggvorbis/m4a files)
-over local and/or remote networks. It listens on a tcp port and
-accepts commands such as play, stop, pause, next from authenticated
-clients. However, there are many more commands.
-
-It supports three built-in network streaming methods (senders): http, dccp,
-or udp.
-
-       * The http sender is recommended for public streams that can be played
-         by any player like mpg123, xmms, itunes, winamp...
-
-       * The dccp sender requires kernel support for the datagram congestion
-         control protocol.
-
-       * The udp sender is recommended for multicast LAN streaming.
-
-It is possible to activate more than one sender simultaneously.
-
-The built-in audio file selector of paraslash is used to manage your
-audio files. It maintains statistics on the usage of all available audio
-files such as last played time, and the number of times each file was
-selected.
-
-Its features include
-
-       * attributes. Allow fine-grained audio file selection.
-
-       * image table. For storing e.g. album cover art.
-
-       * lyrics table. For storing lyrics.
-
-       * playlist table. Stores arbitrary many playlists for later use.
-
-       * mood mode. Audio file selection works by specifying mood
-         methods involving attributes, pattern matching for file names
-         and more. This allows rather sophisticated configurations
-         and is explained in more detail in
-<<
-<a href="README.afs.html"> README.afs </a>
->>
-
-       * rename detection. If files are moved or renamed, afs will
-         recognize them despite of this change.
-
-Despite of all these features, paraslash is lightweight. The
-stripped binary of para_server with all its features compiled in
-mp3/ogg/aac support, http/dccp/udp support) is about 160K on i386
-under Linux. para_audiod (see below) is even smaller.
-
------------
-para_client
------------
-
-The client program to connect to para_server.  paraslash commands
-are sent to para_server and the response is dumped to stdout. This
-can be used by any scripting language to produce user interfaces with
-little programming effort.
-
-All connections between para_server and para_client are encrypted by
-default.  For each user of paraslash you must create a public/secret
-RSA key pair for authentication. The authenticated connection is
-encrypted with a symmetric rc4 session key.
-
----------
-para_recv
----------
-
-A command line http/dccp/udp stream grabber. The http mode of this
-tool can be used to receive data from any http streaming source.
-
------------
-para_filter
------------
-
-A filter program that converts from stdin and writes to stdout.
-
-para_filter combines several decoders (mp3, oggvorbis, aac) and a
-volume normalizer. New filters can be added easily. It is possible
-to "chain" any number of filters, like UNIX pipes.
-
-para_filter does not depend on other parts of paraslash, so it can
-be used as a stand-alone command line tool for audio decoding and
-volume normalization.
-
---------
-para_afh
---------
-
-A small stand-alone program that prints tech info about the given
-audio file to stdout. It can be instructed to print a "chunk table",
-an array of offsets within the audio file or to write the content of
-the audio file in complete chunks 'just in time'.
-
-This allows third-party streaming software that is unaware of
-the particular audio format to send complete frames in real
-time. Currently, mp3, ogg vorbis and aac are supported.
-
-----------
-para_write
-----------
-
-A modular audio stream writer. It supports a simple file writer
-output plug-in and optional wav/raw players for ALSA (Linux) and for
-coreaudio (Mac OS). para_write can also be used as a stand-alone wav
-or raw audio player.
-
------------
-para_audiod
------------
-
-The local daemon that collects information from para_server.
-
-It runs on the client side and connects to para_server.  As soon as
-para_server announces the availability of an audio stream, para_audiod
-starts an appropriate receiver, any number of filters and a paraslash
-writer to play the stream. It is possible to capture the stream at
-any position in the filter chain.
-
-Moreover, para_audiod listens on a local socket and sends status
-information about para_server and para_audiod to local clients on
-request. Access via this local socket may be restricted by using Unix
-socket credentials, if available.
-
------------
-para_audioc
------------
-
-The client program which talks to para_audiod. Used to control
-para_audiod, to receive status info, or to grab the stream at any
-point in the filter chain.
-
-para_audioc (hence para_audiod) is needed by para_gui see below.
-
---------
-para_gui
---------
-
-Themable ncurses-based gui. It calls para_audioc and presents
-the obtained information in an ncurses window. para_gui provides
-key-bindings for the most common commands and new key-bindings can
-be added easily.
-
----------
-para_fade
----------
-
-A (oss-only) alarm clock and volume-fader.
-
----------------
-bash_completion
----------------
-
-A small bash script for inclusion in ~/.bashrc. It gives you command
-line completion for some paraslash commands.
 
 -------
 LICENSE
diff --git a/README.afs b/README.afs
deleted file mode 100644 (file)
index ca01df6..0000000
+++ /dev/null
@@ -1,256 +0,0 @@
-The audio file selector
-=======================
-
-Paraslash comes with a sophisticated audio file selector called *afs*.
-In the
-<<
-<a href="INSTALL.html">installation notes</a>,
->>
-only the "dummy" mode of afs was used which gets activated automatically if
-nothing else was specified. In this section the various features of afs are
-described.
-
-----------
-Attributes
-~~~~~~~~~~
-
-An attribute is simply a bit which can be set for each audio
-file individually.  Up to 64  different attributes may be
-defined. For example, "pop", "rock", "blues", "jazz", "instrumental",
-"german_lyrics", "speech", whatever. It's up to you how many attributes
-you define and how you call them.
-
-A new attribute "test" is created by
-
-       para_client addatt test
-and
-       para_client lsatt
-
-lists all available attributes. You can set the "test" attribute for
-an audio file by executing
-
-       para_client setatt test+ /path/to/the/audio/file
-
-Similarly, the "test" bit can be removed from an audio file with
-
-       para_client setatt test- /path/to/the/audio/file
-
-Instead of a path you may use a shell wildcard pattern. The attribute
-is applied to all audio files matching that pattern:
-
-       para_client setatt test+ '/test/directory/*'
-
-The command
-
-       para_client -- ls -lv
-
-gives you a verbose listing of your audio files which contains also
-which attributes are set.
-
-In case you wonder why the double-dash in the above command is needed:
-It tells para_client to not interpret the options after the dashes. If
-you find this annoying, just say
-
-       alias para='para_client --'
-
-and be happy. In what follows we shall use this alias.
-
-The "test" attribute can be dropped from the database with
-
-       para rmatt test
-
-Read the output of
-
-       para help ls
-       para help setatt
-
-for more information and a complete list of command line options to
-these commands.
-
-
-----------------------
-Abstract mood nonsense
-~~~~~~~~~~~~~~~~~~~~~~
-
-[skip this part if you don't like formal definitions]
-
-A mood consists of a unique name and its *mood definition*, which is
-a set of *mood lines* containing expressions in terms of attributes
-and other data contained in the database.
-
-A mood defines a subset of audio files called the *admissible audio
-files* for that mood. At any time, at most one mood can be *active*
-which means that para_server is going to select only files from that
-subset of admissible files.
-
-So in order to create a mood definition one has to write a set of
-mood lines. Mood lines come in three flavours: Accept lines, deny
-lines and score lines.
-
-The general syntax of the three types of mood lines is
-
-
-       accept [with score <score>] [if] [not] <mood_method> [options]
-       deny [with score <score>] [if] [not] <mood_method> [options]
-       score <score>  [if] [not] <mood_method> [options]
-
-
-Here <score> is either an integer or the string "random" which assigns
-a random score to all matching files. The score value changes the
-order in which admissible files are going to be selected, but is of
-minor importance for this introduction.
-
-So we concentrate on the first two forms, i.e. accept and deny
-lines. As usual, everything in square brackets is optional, i.e.
-accept/deny lines take the following form when ignoring scores:
-
-       accept [if] [not] <mood_method> [options]
-
-and analogously for the deny case. The "if" keyword is purely cosmetic
-and has no function. The "not" keyword just inverts the result, so
-the essence of a mood line is the mood method part and the options
-following thereafter.
-
-A *mood method* is realized as a function which takes an audio file
-and computes a number from the data contained in the database.
-If this number is non-negative, we say the file *matches* the mood
-method. The file matches the full mood line if it either
-
-       - matches the mood method and the "not" keyword is not given,
-or
-       - does not match the mood method, but the "not" keyword is given.
-
-The set of admissible files for the whole mood is now defined as those
-files which match at least one accept mood line, but no deny mood line.
-More formally, an audio file F is admissible if and only if
-
-       (F ~ AL1 or F ~ AL2...) and not (F ~ DL1 or F ~ DN2 ...)
-
-where AL1, AL2... are the accept lines, DL1, DL2... are the deny
-lines and "~" means "matches".
-
-The cases where no mood lines of accept/deny type are defined need
-special treatment:
-
-       - Neither accept nor deny lines: This treats all files as admissible
-         (in fact, that is the definition of the dummy mood which is activated
-         automatically if no moods are available).
-
-       - Only accept lines: A file is admissible iff it matches at least one
-         accept line:
-
-               F ~ AL1 or F ~ AL2 or ...
-
-       - Only deny lines: A file is admissible iff it matches no deny line:
-
-               not (F ~ DL1 or F ~ DN2 ...)
-
-
-
---------------------
-List of mood_methods
-~~~~~~~~~~~~~~~~~~~~
-
-       no_attributes_set
-
-Takes no arguments and matches an audio file if and only if no
-attributes are set.
-
-       played_rarely
-
-Takes no arguments and matches all audio files where the number of
-times this audio file was selected is below the average.
-
-       is_set attribute_name
-
-Takes the name of an attribute and matches iff that attribute is set.
-
-       path_matches pattern
-
-Takes a filename pattern and matches iff the path of the audio file
-matches the pattern.
-
-
-----------
-Mood usage
-~~~~~~~~~~
-
-To create a new mood called "my_mood", write its definition into
-some temporary file, say "tmpfile", and add it to the mood table
-by executing
-
-       para addmood my_mood < tmpfile
-
-If the mood definition is really short, you may just pipe it to the
-client instead of using temporary files. Like this:
-
-       echo "$MOOD_DEFINITION" | para addmood my_mood
-
-There is no need to keep the temporary file since you can always use
-the catmood command to get it back:
-
-       para catmood my_mood
-
-A mood can be activated by executing
-
-       para select m/my_mood
-
-Once active, the list of admissible files is shown by the ls command
-if the "-a" switch is given:
-
-       para ls -a
-
------------------------
-Example mood definition
-~~~~~~~~~~~~~~~~~~~~~~~
-
-Suppose you have defined attributes "punk" and "rock" and want to define
-a mood containing only Punk-Rock songs. That is, an audio file should be
-admissible if and only if both attributes are set. Since
-
-       punk and rock
-
-is obviously the same as
-
-       not (not punk or not rock)
-
-(de Morgan's rule), a mood definition that selects only Punk-Rock
-songs is
-
-       deny if not is_set punk
-       deny if not is_set rock
-
-
----------
-Troubles?
----------
-
-Use the debug loglevel (option -l debug for most commands) to show
-debugging info. Almost all paraslash executables have a brief online
-help which is displayed by using the -h switch. The --detailed-help
-option prints the full help text.
-
-para_fsck tries to fix your database. Use --force (even if your name
-isn't Luke) to clean up after a crash. However, first make sure
-para_server isn't running before executing para_fsck if para_fsck
-complains about busy (dirty) tables. para_fsck also contains an option
-to dump the contents of the database to the file system.
-
-If you don't mind to recreate your database you can start
-from scratch by removing the entire database directory, i.e.
-
-       rm -rf ~/.paraslash/afs_database
-
-Note that this removes all tables, in particular attribute definitions
-and data, and all playlist and mood definitions.
-
-para_fsck operates on the osl-layer, i.e. it fixes inconsistencies
-in the database but doesn't know about the contents of the tables
-contained therein. Use
-
-       para_client check
-
-to print out bad entries, e.g.missing audio files or invalid mood
-definitions.
-
-Still having problems? mailto: Andre Noll <maan@systemlinux.org>
diff --git a/REQUIREMENTS b/REQUIREMENTS
deleted file mode 100644 (file)
index c1743fd..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-Requirements
-============
-
-In any case you need
-
-       - gcc, the gnu compiler collection (shipped with distro): gcc-3.3
-         or newer is required.
-       - gnu make (shipped with disto, might be called gmake)
-       - bash (most likely already installed)
-       - A decent version of grep. Solaris' /bin/grep is not good enough,
-         /usr/xpg4/bin/grep is fine though.
-       - openssl (needed by server, client): usually shipped with
-         distro, but you might have to install the "development package"
-         (called libssl-dev on debian systems) as well:
-         http://www.openssl.org/
-       - help2man (for man page creation) ftp://ftp.gnu.org/pub/gnu/help2man
-       - software mixing, e.g. ALSA and the direct mixing plugin (dmix)
-
-Optional features:
-
-       - *mp3*: The mp3 decoder of para_filter is based on libmad:
-         http://www.underbit.com/products/mad/. If you prefer to
-         use the libmad package provided by your distributor, make
-         sure to install the corresponding development package as
-         well. It is called libmad0-dev on debian-based systems.
-         Note that libmad is not necessary for the server side,
-         i.e. for sending mp3 files.
-
-       - *id3 tags*:
-         For version-2 id3 tag support, you'll need libid3tag which
-         is also available through the above link (alternatively:
-         install package libid3tag0-dev). Without libid3tag, only
-         version one tags are recognized.
-
-       - *ogg vorbis*: For ogg vorbis streams you'll need libogg,
-         libvorbis, libvorbisfile: http://www.xiph.org/downloads/.
-         The corresponding Debian packages are called libogg-dev
-         libvorbis-dev, other distributors chose similar names.
-
-       - *aac*:
-         For aac files (m4a) you'll need libfaad. Get it at
-         http://www.audiocoding.com/.
-         Debian package: libfaad-dev.
-
-       - On Linux, you'll need to have ALSA's development package
-         installed. The Debian package is called libasound2-dev.
-
-Hacking the source:
-
-       - gengetopt: ftp://ftp.gnu.org/pub/gnu/gengetopt/
-       - autoconf: ftp://ftp.gnu.org/pub/gnu/autoconf/
-       - git http://git.or.cz/
-       - grutatxt http://www.triptico.com/software/grutatxt.html
-       - doxygen http://www.stack.nl/~dimitri/doxygen/
-       - global ftp://ftp.gnu.org/pub/gnu/global
-       - m4: ftp://ftp.gnu.org/pub/gnu/m4/
diff --git a/aac.h b/aac.h
index 1efe75a..0a4db41 100644 (file)
--- a/aac.h
+++ b/aac.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2006-2009 Andre Noll <maan@systemlinux.org>
+ * Copyright (C) 2006-2010 Andre Noll <maan@systemlinux.org>
  *
  * Licensed under the GPL v2. For licencing details see COPYING.
  */
index 80b50a4..3e39433 100644 (file)
--- a/aac_afh.c
+++ b/aac_afh.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2006-2008 Andre Noll <maan@systemlinux.org>
+ * Copyright (C) 2006-2010 Andre Noll <maan@systemlinux.org>
  *
  * Licensed under the GPL v2. For licencing details see COPYING.
  */
 
 /** \file aac_afh.c para_server's aac audio format handler */
 
+#include <regex.h>
+
 #include "para.h"
 #include "error.h"
-#include "string.h"
 #include "afh.h"
-#include "afs.h"
-#include "server.h"
+#include "string.h"
 #include "aac.h"
 
 static int aac_find_stsz(unsigned char *buf, size_t buflen, off_t *skip)
@@ -91,11 +91,9 @@ static char *get_tag(unsigned char *p, int size)
        return buf;
 }
 
-static char *read_tags(unsigned char *buf, size_t buflen)
+static void read_tags(unsigned char *buf, size_t buflen, struct afh_info *afhi)
 {
        unsigned char *p = buf;
-       char *title = NULL, *artist = NULL, *album = NULL, *year = NULL,
-               *comment = NULL, *result;
 
        while (p + 32 < buf + buflen) {
                unsigned char *q, type1[5], type2[5];
@@ -114,27 +112,20 @@ static char *read_tags(unsigned char *buf, size_t buflen)
                if (q + size2 > buf + buflen)
                        break;
                if (!atom_cmp(type1, "©ART"))
-                       artist = get_tag(q, size2);
+                       afhi->tags.artist = get_tag(q, size2);
                else if (!atom_cmp(type1, "©alb"))
-                       album = get_tag(q, size2);
+                       afhi->tags.album = get_tag(q, size2);
                else if (!atom_cmp(type1, "©nam"))
-                       title = get_tag(q, size2);
+                       afhi->tags.title = get_tag(q, size2);
                else if (!atom_cmp(type1, "©cmt"))
-                       comment = get_tag(q, size2);
+                       afhi->tags.comment = get_tag(q, size2);
                else if (!atom_cmp(type1, "©day"))
-                       year = get_tag(q, size2);
+                       afhi->tags.year = get_tag(q, size2);
                p += size1;
        }
-       result = make_taginfo(title, artist, album, year, comment);
-       free(title);
-       free(artist);
-       free(album);
-       free(year);
-       free(comment);
-       return result;
 }
 
-static char *read_meta(unsigned char *buf, size_t buflen)
+static void read_meta(unsigned char *buf, size_t buflen, struct afh_info *afhi)
 {
        unsigned char *p = buf;
 
@@ -145,12 +136,12 @@ static char *read_meta(unsigned char *buf, size_t buflen)
                        continue;
                }
                p += 4;
-               return read_tags(p, buflen - (p - buf));
+               return read_tags(p, buflen - (p - buf), afhi);
        }
-       return make_taginfo(NULL, NULL, NULL, NULL, NULL);
 }
 
-static char *aac_get_taginfo(unsigned char *buf, size_t buflen)
+static void aac_get_taginfo(unsigned char *buf, size_t buflen,
+               struct afh_info *afhi)
 {
        int i;
        uint64_t subsize;
@@ -165,10 +156,9 @@ static char *aac_get_taginfo(unsigned char *buf, size_t buflen)
                p = buf + i;
                i += read_atom_header(p, &subsize, type);
                p = buf + i;
-               return read_meta(p, buflen - i);
+               return read_meta(p, buflen - i, afhi);
        }
        PARA_INFO_LOG("no meta data\n");
-       return make_taginfo(NULL, NULL, NULL, NULL, NULL);
 }
 
 static ssize_t aac_compute_chunk_table(struct afh_info *afhi,
@@ -232,12 +222,11 @@ static int aac_get_file_info(char *map, size_t numbytes, __a_unused int fd,
        mp4AudioSpecificConfig mp4ASC;
        NeAACDecHandle handle = NULL;
        unsigned char *umap = (unsigned char *) map;
-       char *taginfo;
 
        ret = aac_find_esds(umap, numbytes, &skip, &decoder_len);
        if (ret < 0)
                goto out;
-       taginfo = aac_get_taginfo(umap, numbytes);
+       aac_get_taginfo(umap, numbytes, afhi);
        handle = aac_open();
        ret = -E_AAC_AFH_INIT;
        if (NeAACDecInit(handle, umap + skip, decoder_len, &rate, &channels))
@@ -268,11 +257,6 @@ static int aac_get_file_info(char *map, size_t numbytes, __a_unused int fd,
        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);
-       afhi->info_string = make_message("%s:\n%s",
-               status_item_list[SI_AUDIO_FILE_INFO],
-               taginfo);
-       free(taginfo);
-       tv_scale(20, &afhi->chunk_tv, &afhi->eof_tv);
        ret = 1;
 out:
        if (handle)
index 2235236..68c3c89 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2006-2009 Andre Noll <maan@systemlinux.org>
+ * Copyright (C) 2006-2010 Andre Noll <maan@systemlinux.org>
  *
  * Licensed under the GPL v2. For licencing details see COPYING.
  */
index 1ceca1f..209c9a4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2006-2009 Andre Noll <maan@systemlinux.org>
+ * Copyright (C) 2006-2010 Andre Noll <maan@systemlinux.org>
  *
  * Licensed under the GPL v2. For licencing details see COPYING.
  */
 
 /** \file aacdec_filter.c paraslash's aac (m4a) decoder. */
 
-#include "para.h"
+#include <regex.h>
+#include <stdbool.h>
 
+#include "para.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"
 
-/** the output buffer size */
-#define AAC_OUTBUF_SIZE (32 * 1024)
-
-/** give up decoding after that many errors */
+/** Give up decoding after that many errors. */
 #define MAX_ERRORS 20
 
 /**
@@ -49,25 +49,71 @@ struct private_aacdec_data {
        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. */
+       unsigned int samplerate;
 };
 
-static ssize_t aacdec(char *input_buffer, size_t len, struct filter_node *fn)
+static int aacdec_execute(struct btr_node *btrn, const char *cmd, char **result)
+{
+       struct filter_node *fn = btr_context(btrn);
+       struct private_aacdec_data *padd = fn->private_data;
+
+       if (!strcmp(cmd, "samplerate")) {
+               if (padd->samplerate == 0)
+                       return -E_BTR_NAVAIL;
+               *result = make_message("%u", padd->samplerate);
+               return 1;
+       }
+       if (!strcmp(cmd, "channels")) {
+               if (padd->channels == 0)
+                       return -E_BTR_NAVAIL;
+               *result = make_message("%u", padd->channels);
+               return 1;
+       }
+       return -ERRNO_TO_PARA_ERROR(ENOTSUP);
+}
+
+static void aacdec_open(struct filter_node *fn)
+{
+       struct private_aacdec_data *padd = para_calloc(sizeof(*padd));
+
+       fn->private_data = padd;
+       fn->min_iqs = 2048;
+       padd->handle = aac_open();
+}
+
+static void aacdec_close(struct filter_node *fn)
+{
+       struct private_aacdec_data *padd = fn->private_data;
+
+       NeAACDecClose(padd->handle);
+       free(padd);
+       fn->private_data = NULL;
+}
+
+static void aacdec_post_select(__a_unused struct sched *s, struct task *t)
 {
+       struct filter_node *fn = container_of(t, struct filter_node, task);
+       struct btr_node *btrn = fn->btrn;
        struct private_aacdec_data *padd = fn->private_data;
-       struct filter_chain *fc = fn->fc;
        int i, ret;
-       unsigned char *p, *outbuffer;
-       unsigned char *inbuf = (unsigned char*)input_buffer;
-       size_t skip, consumed = 0;
+       unsigned char *p, *inbuf, *outbuffer;
+       char *btr_buf;
+       size_t len, skip, consumed, loaded, iqs;
 
-       if (fn->loaded > fn->bufsize * 3 / 5)
-               return 0;
-       ret = *fc->input_error;
+next_buffer:
+       t->error = 0;
+       ret = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL);
        if (ret < 0)
-               return ret;
-       if (len < 2048)
-               return 0;
-
+               goto err;
+       if (ret == 0)
+               return;
+       btr_merge(btrn, fn->min_iqs);
+       len = btr_next_buffer(btrn, (char **)&inbuf);
+       consumed = 0;
+       iqs = btr_get_input_queue_size(btrn);
        if (!padd->initialized) {
                unsigned long rate = 0;
                unsigned char channels = 0;
@@ -93,10 +139,10 @@ static ssize_t aacdec(char *input_buffer, size_t len, struct filter_node *fn)
                                        &channels) < 0)
                                goto out;
                }
-               fc->samplerate = rate;
-               fc->channels = channels;
+               padd->samplerate = rate;
+               padd->channels = channels;
                PARA_INFO_LOG("rate: %u, channels: %d\n",
-                       fc->samplerate, fc->channels);
+                       padd->samplerate, padd->channels);
                padd->initialized = 1;
        }
        if (padd->decoder_length > 0) {
@@ -124,72 +170,64 @@ static ssize_t aacdec(char *input_buffer, size_t len, struct filter_node *fn)
        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, p,
                len - consumed);
        if (padd->frame_info.error) {
+               int err = padd->frame_info.error;
                ret = -E_AAC_DECODE;
                if (padd->error_count++ > MAX_ERRORS)
-                       goto out;
-               PARA_ERROR_LOG("frame_error: %d, consumed: %zu + %zd + %lu\n",
-                       padd->frame_info.error, padd->consumed_total,
-                       consumed, padd->frame_info.bytesconsumed);
+                       goto err;
+               PARA_ERROR_LOG("frame_error: %d (%s), consumed: %zu + %zd + %lu\n",
+                       err, NeAACDecGetErrorMessage(padd->frame_info.error),
+                       padd->consumed_total, consumed,
+                       padd->frame_info.bytesconsumed);
                PARA_ERROR_LOG("%s\n", NeAACDecGetErrorMessage(
                        padd->frame_info.error));
                consumed++; /* catch 21 */
                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;
-       ret = -E_AAC_OVERRUN;
-       if (padd->frame_info.samples * 2 + fn->loaded > fn->bufsize)
-               goto out;
+       btr_buf = para_malloc(2 * padd->frame_info.samples);
+       loaded = 0;
        for (i = 0; i < padd->frame_info.samples; i++) {
-               short *s = (short *)outbuffer;
-               write_int16_host_endian(fn->buf + fn->loaded, s[i]);
-               fn->loaded += 2;
+               short sh = ((short *)outbuffer)[i];
+               write_int16_host_endian(btr_buf + loaded, sh);
+               loaded += 2;
        }
+       btr_add_output(btr_buf, loaded, btrn);
 success:
        ret = consumed;
 out:
-       if (ret > 0)
+       if (ret >= 0) {
                padd->consumed_total += ret;
-       return ret;
-}
-
-static void aacdec_open(struct filter_node *fn)
-{
-       struct private_aacdec_data *padd = para_calloc(sizeof(*padd));
-
-       fn->private_data = padd;
-       fn->bufsize = AAC_OUTBUF_SIZE;
-       fn->buf = para_calloc(fn->bufsize);
-       padd->handle = aac_open();
-}
-
-static void aacdec_close(struct filter_node *fn)
-{
-       struct private_aacdec_data *padd = fn->private_data;
-
-       NeAACDecClose(padd->handle);
-       free(fn->buf);
-       fn->buf = NULL;
-       free(padd);
-       fn->private_data = NULL;
+               btr_consume(btrn, ret);
+               goto next_buffer;
+       }
+err:
+       assert(ret < 0);
+       t->error = ret;
+       btr_remove_node(btrn);
 }
 
 /**
- * the init function of the aacdec filter
+ * The init function of the aacdec filter.
  *
- * \param f pointer to the filter struct to initialize
+ * \param f Pointer to the filter struct to initialize.
  *
  * \sa filter::init
  */
 void aacdec_filter_init(struct filter *f)
 {
        f->open = aacdec_open;
-       f->convert = aacdec;
        f->close = aacdec_close;
+       f->pre_select = generic_filter_pre_select;
+       f->post_select = aacdec_post_select;
+       f->execute = aacdec_execute;
 }
diff --git a/acl.c b/acl.c
index ffcd168..02df1e4 100644 (file)
--- a/acl.c
+++ b/acl.c
@@ -1,11 +1,13 @@
 /*
- * Copyright (C) 2005-2009 Andre Noll <maan@systemlinux.org>
+ * Copyright (C) 2005-2010 Andre Noll <maan@systemlinux.org>
  *
  * Licensed under the GPL v2. For licencing details see COPYING.
  */
 
 /** \file acl.c Access control lists for paraslash senders. */
 
+#include <regex.h>
+
 #include "para.h"
 #include "error.h"
 #include "string.h"
diff --git a/acl.h b/acl.h
index e6090a8..65ccc74 100644 (file)
--- a/acl.h
+++ b/acl.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008-2009 Andre Noll <maan@systemlinux.org>
+ * Copyright (C) 2008-2010 Andre Noll <maan@systemlinux.org>
  *
  * Licensed under the GPL v2. For licencing details see COPYING.
  */
diff --git a/afh.c b/afh.c
index 8e1e6b3..b38dfbb 100644 (file)
--- a/afh.c
+++ b/afh.c
@@ -1,11 +1,12 @@
 /*
- * Copyright (C) 2008-2009 Andre Noll <maan@systemlinux.org>
+ * Copyright (C) 2008-2010 Andre Noll <maan@systemlinux.org>
  *
  * Licensed under the GPL v2. For licencing details see COPYING.
  */
 
 /** \file afh.c Paraslash's standalone audio format handler tool. */
 
+#include <regex.h>
 #include <dirent.h>
 #include <sys/time.h>
 
@@ -32,18 +33,28 @@ static void print_info(int audio_format_num, struct afh_info *afhi)
                "%s: %dHz\n" /* frequency */
                "%s: %d\n" /* channels */
                "%s: %lu\n" /* seconds total */
-               "%s" /* tag info */
                "%s: %lu: %lu\n" /* chunk time */
-               "%s: %lu\n", /* num chunks */
+               "%s: %lu\n" /* num chunks */
+               "%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,
-               afhi->info_string,
                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_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 : ""
        );
 }
 
@@ -51,10 +62,25 @@ static void print_chunk_table(struct afh_info *afhi)
 {
        int i;
 
-       printf("chunk_table: ");
-       for (i = 0; i <= afhi->chunks_total; i++)
-               printf("%u ", afhi->chunk_table[i]);
-       printf("\n");
+       if (!conf.human_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++) {
+               struct timeval tv;
+               long unsigned from, to;
+               tv_scale(i - 1, &afhi->chunk_tv, &tv);
+               from = tv2ms(&tv);
+               tv_scale(i, &afhi->chunk_tv, &tv);
+               to = tv2ms(&tv);
+               printf("%d [%lu.%03lu - %lu.%03lu] %u - %u (%u)\n", i,
+                       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]);
+       }
 }
 
 static int cat_file(void *audio_file_data, struct afh_info *afhi)
@@ -166,10 +192,15 @@ int main(int argc, char **argv)
                        print_info(audio_format_num, &afhi);
                        if (conf.chunk_table_given)
                                print_chunk_table(&afhi);
+                       free(afhi.techinfo);
+                       free(afhi.tags.artist);
+                       free(afhi.tags.title);
+                       free(afhi.tags.year);
+                       free(afhi.tags.album);
+                       free(afhi.tags.comment);
+                       free(afhi.chunk_table);
                        printf("\n");
                }
-               free(afhi.chunk_table);
-               free(afhi.info_string);
                ret2 = para_munmap(audio_file_data, audio_file_size);
                if (ret2 < 0 && ret >= 0)
                        ret = ret2;
diff --git a/afh.h b/afh.h
index 35cc7ef..95cd051 100644 (file)
--- a/afh.h
+++ b/afh.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2005-2009 Andre Noll <maan@systemlinux.org>
+ * Copyright (C) 2005-2010 Andre Noll <maan@systemlinux.org>
  *
  * Licensed under the GPL v2. For licencing details see COPYING.
  */
 #define AAC_AUDIO_FORMAT ""
 #endif
 
-#define SUPPORTED_AUDIO_FORMATS "mp3" OV_AUDIO_FORMAT AAC_AUDIO_FORMAT
+#define SUPPORTED_AUDIO_FORMATS "mp3 " OV_AUDIO_FORMAT AAC_AUDIO_FORMAT " wma "
 
 /** \endcond */
 
+/**
+ * The tags used by all audio format handlers.
+ *
+ * Paraslash only uses the more common tags. These are recognized
+ * for all supported audio formats.
+ */
+struct taginfo {
+       /** TPE1 (id3v2) / ARTIST (vorbis) / ©ART (aac) */
+       char *artist;
+       /** TIT2/TITLE/©nam */
+       char *title;
+       /** TDRC/YEAR/©day */
+       char *year;
+       /** TALB/ALBUM/©alb */
+       char *album;
+       /** COMM/COMMENT/©cmt */
+       char *comment;
+};
+
 /** Audio format dependent information. */
 struct afh_info {
        /** The number of chunks this audio file contains. */
        long unsigned chunks_total;
        /** The length of the audio file in seconds. */
        long unsigned seconds_total;
-       /** A string that gets filled in by the audio format handler. */
-       char *info_string;
+       /** Audio handler specific info about the file. */
+       char *techinfo;
+       /** Id3 tags, vorbis comments, aac tags. */
+       struct taginfo tags;
        /**
         * The table that specifies the offset of the individual pieces in
         * the current audio file.
@@ -38,8 +59,6 @@ struct afh_info {
        uint32_t *chunk_table;
        /** Period of time between sending data chunks. */
        struct timeval chunk_tv;
-       /** End of file timeout - Do not load new audio file until this time. */
-       struct timeval eof_tv;
        /**
         * The position of the header within the audio file. Ignored if \a
         * header_len equals zero.
@@ -104,5 +123,3 @@ void afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi,
                void *map, const char **buf, size_t *len);
 uint32_t afh_get_largest_chunk_size(struct afh_info *afhi);
 void afh_get_header(struct afh_info *afhi, void *map, const char **buf, size_t *len);
-char *make_taginfo(char *title, char *artist, char *album, char *year,
-               char *comment);
index 7d4ab08..f28acaa 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 1997-2009 Andre Noll <maan@systemlinux.org>
+ * Copyright (C) 1997-2010 Andre Noll <maan@systemlinux.org>
  *
  * Licensed under the GPL v2. For licencing details see COPYING.
  */
@@ -10,6 +10,7 @@
 #include <sys/time.h> /* gettimeofday */
 #include <sys/types.h>
 #include <dirent.h>
+#include <regex.h>
 
 #include "para.h"
 #include "error.h"
@@ -26,6 +27,7 @@ void mp3_init(struct audio_format_handler *);
        void aac_afh_init(struct audio_format_handler *);
 #endif
 
+void wma_afh_init(struct audio_format_handler *);
 /**
  * The list of supported audio formats.
  *
@@ -55,6 +57,10 @@ static struct audio_format_handler afl[] = {
                .init = aac_afh_init,
 #endif
        },
+       {
+               .name = "wma",
+               .init = wma_afh_init,
+       },
        {
                .name = NULL,
        }
@@ -121,39 +127,6 @@ int guess_audio_format(const char *name)
        return -E_AUDIO_FORMAT;
 }
 
-/**
- * Pretty-print the given meta-info.
- *
- * \param title The title of the audio file.
- * \param artist The artist.
- * \param album The name of the album.
- * \param year Year of release.
- * \param comment Further comments.
- *
- * This function is called by each audio format handler to produce the tag info
- * status items. Usually, the audio format handlers read this info from the
- * audio file (id3 tags, vorbis comments, ...).
- *
- * It is OK to pass \p NULL pointers for any argument in which case a suitable
- * string is inserted which indicates that this information is not available.
- *
- * \return The status item string. It must be freed by the caller.
- */
-char *make_taginfo(char *title, char *artist, char *album, char *year,
-               char *comment)
-{
-       return make_message("%s: %s, by %s\n" /* taginfo1 */
-               "%s: A: %s, Y: %s, C: %s\n", /* taginfo2 */
-               status_item_list[SI_TAGINFO1],
-               (title && *title)? title : "(title tag not set)",
-               (artist && *artist)? artist : "(artist tag not set)",
-               status_item_list[SI_TAGINFO2],
-               (album && *album)?  album : "(album tag not set)",
-               (year && *year)? year : "????",
-               (comment && *comment)? comment : "(comment tag not set)"
-       );
-}
-
 /**
  * Call get_file_info() to obtain an afhi structure.
  *
@@ -180,22 +153,52 @@ int compute_afhi(const char *path, char *data, size_t size, int fd,
 
        afhi->header_offset = 0;
        afhi->header_len = 0;
+       afhi->techinfo = NULL;
+       afhi->tags.artist = NULL;
+       afhi->tags.title = NULL;
+       afhi->tags.year = NULL;
+       afhi->tags.album = NULL;
+       afhi->tags.comment = NULL;
        format = guess_audio_format(path);
 
        if (format >= 0) {
                ret = afl[format].get_file_info(data, size, fd, afhi);
-               if (ret >= 0)
-                       return format;
+               if (ret >= 0) {
+                       ret = format;
+                       goto success;
+               }
        }
        FOR_EACH_AUDIO_FORMAT(i) {
                if (i == format) /* we already tried this one to no avail */
                        continue;
                ret = afl[i].get_file_info(data, size, fd, afhi);
-               if (ret >= 0)
-                       return i;
+               if (ret >= 0) {
+                       ret = i;
+                       goto success;
+               }
                PARA_WARNING_LOG("%s\n", para_strerror(-ret));
        }
        return -E_AUDIO_FORMAT;
+success:
+       if (!afhi->techinfo)
+               afhi->techinfo = para_strdup(NULL);
+       if (!afhi->tags.artist)
+               afhi->tags.artist = para_strdup(NULL);
+       if (!afhi->tags.title)
+               afhi->tags.title = para_strdup(NULL);
+       if (!afhi->tags.year)
+               afhi->tags.year = para_strdup(NULL);
+       if (!afhi->tags.album)
+               afhi->tags.album = para_strdup(NULL);
+       if (!afhi->tags.comment)
+               afhi->tags.comment = para_strdup(NULL);
+       PARA_DEBUG_LOG("techinfo: %s\n", afhi->techinfo);
+       PARA_DEBUG_LOG("artist: %s\n", afhi->tags.artist);
+       PARA_DEBUG_LOG("title: %s\n", afhi->tags.title);
+       PARA_DEBUG_LOG("year: %s\n", afhi->tags.year);
+       PARA_DEBUG_LOG("album: %s\n", afhi->tags.album);
+       PARA_DEBUG_LOG("comment: %s\n", afhi->tags.comment);
+       return ret;
 }
 
 /**
@@ -233,6 +236,13 @@ void afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi,
        *len = afhi->chunk_table[chunk_num + 1] - pos;
 }
 
+/**
+ * Compute the size of the largest chunk of an audio file.
+ *
+ * \param afhi The audio format handler struct containing the chunk table.
+ *
+ * \return The number of bytes of the largest chunk.
+ */
 uint32_t afh_get_largest_chunk_size(struct afh_info *afhi)
 {
        uint32_t n, largest = 0, *ct = afhi->chunk_table;
@@ -252,7 +262,7 @@ uint32_t afh_get_largest_chunk_size(struct afh_info *afhi)
  *
  * This function sets \a buf to \p NULL and \a len to zero if \a map or \a
  * afhi is \p NULL, or if the current audio format does not need special
- * header treamtment.
+ * header treatment.
  */
 void afh_get_header(struct afh_info *afhi, void *map, const char **buf, size_t *len)
 {
diff --git a/afs.c b/afs.c
index ccda7cd..2b748f2 100644 (file)
--- a/afs.c
+++ b/afs.c
@@ -1,16 +1,21 @@
 /*
- * Copyright (C) 2007-2009 Andre Noll <maan@systemlinux.org>
+ * Copyright (C) 2007-2010 Andre Noll <maan@systemlinux.org>
  *
  * Licensed under the GPL v2. For licencing details see COPYING.
  */
 
 /** \file afs.c Paraslash's audio file selector. */
 
+#include <regex.h>
 #include <signal.h>
 #include <fnmatch.h>
+#include <openssl/rc4.h>
+#include <osl.h>
+
 #include "server.cmdline.h"
 #include "para.h"
 #include "error.h"
+#include "crypt.h"
 #include "string.h"
 #include "afh.h"
 #include "afs.h"
@@ -24,6 +29,7 @@
 #include "sched.h"
 #include "signal.h"
 #include "fd.h"
+#include "mood.h"
 
 /** The osl tables used by afs. \sa blob.c. */
 enum afs_table_num {
@@ -85,7 +91,6 @@ static struct signal_task signal_task_struct;
 static enum play_mode current_play_mode;
 static char *current_mop; /* mode or playlist specifier. NULL means dummy mooe */
 
-
 /**
  * A random number used to "authenticate" the connection.
  *
@@ -342,7 +347,7 @@ 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_get_object(pmd->table, row, pmd->match_col_num, &name_obj);
+       int ret = osl(osl_get_object(pmd->table, row, pmd->match_col_num, &name_obj));
        const char *pattern_txt = (const char *)pmd->patterns.data;
 
        if (ret < 0)
@@ -369,16 +374,15 @@ static int action_if_pattern_matches(struct osl_row *row, void *data)
  *
  * \param pmd Describes what to match and how.
  *
- * \return The return value of the underlying call to osl_rbtree_loop()
- * or osl_rbtree_loop_reverse().
+ * \return Standard.
  */
 int for_each_matching_row(struct pattern_match_data *pmd)
 {
        if (pmd->pm_flags & PM_REVERSE_LOOP)
-               return osl_rbtree_loop_reverse(pmd->table, pmd->loop_col_num, pmd,
-                       action_if_pattern_matches);
-       return osl_rbtree_loop(pmd->table, pmd->loop_col_num, pmd,
-                       action_if_pattern_matches);
+               return osl(osl_rbtree_loop_reverse(pmd->table, pmd->loop_col_num, pmd,
+                       action_if_pattern_matches));
+       return osl(osl_rbtree_loop(pmd->table, pmd->loop_col_num, pmd,
+                       action_if_pattern_matches));
 }
 
 /**
@@ -403,80 +407,6 @@ int string_compare(const struct osl_object *obj1, const struct osl_object *obj2)
        return strncmp(str1, str2, PARA_MIN(obj1->size, obj2->size));
 }
 
-/*
- * write input from fd to dynamically allocated buffer,
- * but maximal max_size byte.
- */
-static int fd2buf(int fd, unsigned max_size, struct osl_object *obj)
-{
-       const size_t chunk_size = 1024;
-       size_t size = 2048, received = 0;
-       int ret;
-       char *buf = para_malloc(size);
-
-       for (;;) {
-               ret = recv_bin_buffer(fd, buf + received, chunk_size);
-               if (ret <= 0)
-                       break;
-               received += ret;
-               if (received + chunk_size >= size) {
-                       size *= 2;
-                       ret = -E_INPUT_TOO_LARGE;
-                       if (size > max_size)
-                               break;
-                       buf = para_realloc(buf, size);
-               }
-       }
-       obj->data = buf;
-       obj->size = received;
-       if (ret < 0)
-               free(buf);
-       return ret;
-}
-
-/**
- * Read data from a file descriptor, and send it to the afs process.
- *
- * \param fd File descriptor to read data from.
- * \param arg_obj Pointer to the arguments to \a f.
- * \param f The callback function.
- * \param max_len Don't read more than that many bytes from stdin.
- * \param result_handler See \ref send_callback_request.
- * \param private_result_data See \ref send_callback_request.
- *
- * This function is used by commands that wish to let para_server store
- * arbitrary data specified by the user (for instance the add_blob family of
- * commands). First, at most \a max_len bytes are read from \a fd, the result
- * is concatenated with the buffer given by \a arg_obj, and the combined buffer
- * is made available to the afs process via the callback method. See \ref
- * send_callback_request for details.
- *
- * \return Negative on errors, the return value of the underlying call to
- * send_callback_request() otherwise.
- */
-int stdin_command(int fd, struct osl_object *arg_obj, callback_function *f,
-               unsigned max_len, callback_result_handler *result_handler,
-               void *private_result_data)
-{
-       struct osl_object query, stdin_obj;
-       int ret;
-
-       ret = send_buffer(fd, AWAITING_DATA_MSG);
-       if (ret < 0)
-               return ret;
-       ret = fd2buf(fd, max_len, &stdin_obj);
-       if (ret < 0)
-               return ret;
-       query.size = arg_obj->size + stdin_obj.size;
-       query.data = para_malloc(query.size);
-       memcpy(query.data, arg_obj->data, arg_obj->size);
-       memcpy((char *)query.data + arg_obj->size, stdin_obj.data, stdin_obj.size);
-       free(stdin_obj.data);
-       ret = send_callback_request(f, &query, result_handler, private_result_data);
-       free(query.data);
-       return ret;
-}
-
 static int pass_afd(int fd, char *buf, size_t size)
 {
        struct msghdr msg = {.msg_iov = NULL};
@@ -652,21 +582,22 @@ out:
  * Result handler for sending data to the para_client process.
  *
  * \param result The data to be sent.
- * \param fd_ptr Pointer to the file descriptor.
+ * \param private Pointer to rc4 context.
  *
- * \return The return value of the underlying call to send_bin_buffer().
+ * \return The return value of the underlying call to rc4_send_bin_buffer().
  *
- * \sa \ref callback_result_handler.
+ * \sa \ref callback_result_handler, \ref rc4_send_bin_buffer().
  */
-int send_result(struct osl_object *result, void *fd_ptr)
+int rc4_send_result(struct osl_object *result, void *private)
 {
-       int fd = *(int *)fd_ptr;
+       struct rc4_context *rc4c = private;
+
        if (!result->size)
                return 1;
-       return send_bin_buffer(fd, result->data, result->size);
+       return rc4_send_bin_buffer(rc4c, result->data, result->size);
 }
 
-int com_select(int fd, int argc, char * const * const argv)
+int com_select(struct rc4_context *rc4c, int argc, char * const * const argv)
 {
        struct osl_object query;
 
@@ -675,7 +606,7 @@ int com_select(int fd, int argc, char * const * const argv)
        query.data = argv[1];
        query.size = strlen(argv[1]) + 1;
        return send_callback_request(com_select_callback, &query,
-               &send_result, &fd);
+               &rc4_send_result, rc4c);
 }
 
 static void init_admissible_files(char *arg)
@@ -730,7 +661,7 @@ static void get_database_dir(void)
                else {
                        char *home = para_homedir();
                        database_dir = make_message(
-                               "%s/.paraslash/afs_database", home);
+                               "%s/.paraslash/afs_database-0.4", home);
                        free(home);
                }
        }
@@ -778,15 +709,16 @@ static void signal_pre_select(struct sched *s, struct task *t)
 
 static void afs_signal_post_select(struct sched *s, struct task *t)
 {
-       struct signal_task *st = container_of(t, struct signal_task, task);
+       int signum;
+
        if (getppid() == 1) {
                PARA_EMERG_LOG("para_server died\n");
                goto shutdown;
        }
-       if (!FD_ISSET(st->fd, &s->rfds))
+       signum = para_next_signal(&s->rfds);
+       if (signum == 0)
                return;
-       st->signum = para_next_signal();
-       if (st->signum == SIGHUP) {
+       if (signum == SIGHUP) {
                close_afs_tables();
                parse_config_or_die(1);
                t->error = open_afs_tables();
@@ -795,7 +727,7 @@ static void afs_signal_post_select(struct sched *s, struct task *t)
                init_admissible_files(current_mop);
                return;
        }
-       PARA_EMERG_LOG("terminating on signal %d\n", st->signum);
+       PARA_EMERG_LOG("terminating on signal %d\n", signum);
 shutdown:
        sched_shutdown();
        t->error = -E_AFS_SIGNAL;
@@ -911,57 +843,56 @@ static int call_callback(int fd, int query_shmid)
        return shm_detach(query_shm);
 }
 
-static int execute_server_command(void)
+static int execute_server_command(fd_set *rfds)
 {
        char buf[8];
-       int ret = recv_bin_buffer(server_socket, buf, sizeof(buf) - 1);
+       size_t n;
+       int ret = read_nonblock(server_socket, buf, sizeof(buf) - 1, rfds, &n);
 
-       if (ret <= 0) {
-               if (!ret)
-                       ret = -ERRNO_TO_PARA_ERROR(ECONNRESET);
-               goto err;
-       }
-       buf[ret] = '\0';
-       PARA_DEBUG_LOG("received: %s\n", buf);
-       ret = -E_BAD_CMD;
+       if (ret < 0 || n == 0)
+               return ret;
+       buf[n] = '\0';
        if (strcmp(buf, "new"))
-               goto err;
-       ret = open_next_audio_file();
-err:
-       return ret;
+               return -E_BAD_CMD;
+       return open_next_audio_file();
 }
 
-static void execute_afs_command(int fd, uint32_t expected_cookie)
+/* returns 0 if no data available, 1 else */
+static int execute_afs_command(int fd, fd_set *rfds, uint32_t expected_cookie)
 {
        uint32_t cookie;
        int query_shmid;
        char buf[sizeof(cookie) + sizeof(query_shmid)];
-       int ret = recv_bin_buffer(fd, buf, sizeof(buf));
+       size_t n;
+       int ret = read_nonblock(fd, buf, sizeof(buf), rfds, &n);
 
        if (ret < 0)
                goto err;
-       if (ret != sizeof(buf)) {
+       if (n == 0)
+               return 0;
+       if (n != sizeof(buf)) {
                PARA_NOTICE_LOG("short read (%d bytes, expected %lu)\n",
                        ret, (long unsigned) sizeof(buf));
-               return;
+               return 1;
        }
        cookie = *(uint32_t *)buf;
        if (cookie != expected_cookie) {
-               PARA_NOTICE_LOG("received invalid cookie(got %u, expected %u)\n",
+               PARA_NOTICE_LOG("received invalid cookie (got %u, expected %u)\n",
                        (unsigned)cookie, (unsigned)expected_cookie);
-               return;
+               return 1;
        }
        query_shmid = *(int *)(buf + sizeof(cookie));
        if (query_shmid < 0) {
                PARA_WARNING_LOG("received invalid query shmid %d)\n",
                        query_shmid);
-               return;
+               return 1;
        }
        ret = call_callback(fd, query_shmid);
        if (ret >= 0)
-               return;
+               return 1;
 err:
        PARA_NOTICE_LOG("%s\n", para_strerror(-ret));
+       return 1;
 }
 
 /** Shutdown connection if query has not arrived until this many seconds. */
@@ -974,20 +905,16 @@ static void command_post_select(struct sched *s, struct task *t)
        struct afs_client *client, *tmp;
        int fd, ret;
 
-       if (FD_ISSET(server_socket, &s->rfds)) {
-               ret = execute_server_command();
-               if (ret < 0) {
-                       PARA_EMERG_LOG("%s\n", para_strerror(-ret));
-                       sched_shutdown();
-                       return;
-               }
+       ret = execute_server_command(&s->rfds);
+       if (ret < 0) {
+               PARA_EMERG_LOG("%s\n", para_strerror(-ret));
+               sched_shutdown();
+               return;
        }
-
        /* Check the list of connected clients. */
        list_for_each_entry_safe(client, tmp, &afs_client_list, node) {
-               if (FD_ISSET(client->fd, &s->rfds))
-                       execute_afs_command(client->fd, ct->cookie);
-               else { /* prevent bogus connection flooding */
+               ret = execute_afs_command(client->fd, &s->rfds, ct->cookie);
+               if (ret == 0) { /* prevent bogus connection flooding */
                        struct timeval diff;
                        tv_diff(now, &client->connect_time, &diff);
                        if (diff.tv_sec < AFS_CLIENT_TIMEOUT)
@@ -999,14 +926,11 @@ static void command_post_select(struct sched *s, struct task *t)
                free(client);
        }
        /* Accept connections on the local socket. */
-       if (!FD_ISSET(ct->fd, &s->rfds))
-               return;
-       ret = para_accept(ct->fd, &unix_addr, sizeof(unix_addr));
-       if (ret < 0) {
+       ret = para_accept(ct->fd, &s->rfds, &unix_addr, sizeof(unix_addr), &fd);
+       if (ret < 0)
                PARA_NOTICE_LOG("%s\n", para_strerror(-ret));
+       if (ret <= 0)
                return;
-       }
-       fd = ret;
        ret = mark_fd_nonblocking(fd);
        if (ret < 0) {
                PARA_NOTICE_LOG("%s\n", para_strerror(-ret));
@@ -1096,7 +1020,7 @@ out:
        free(pb.buf);
 }
 
-int com_init(int fd, int argc, char * const * const argv)
+int com_init(struct rc4_context *rc4c, int argc, char * const * const argv)
 {
        int i, j, ret;
        uint32_t table_mask = (1 << (NUM_AFS_TABLES + 1)) - 1;
@@ -1121,9 +1045,10 @@ int com_init(int fd, int argc, char * const * const argv)
                                return -E_BAD_TABLE_NAME;
                }
        }
-       ret = send_callback_request(create_tables_callback, &query, &send_result, &fd);
+       ret = send_callback_request(create_tables_callback, &query,
+               rc4_send_result, rc4c);
        if (ret < 0)
-               return send_va_buffer(fd, "%s\n", para_strerror(-ret));
+               return rc4_send_va_buffer(rc4c, "%s\n", para_strerror(-ret));
        return ret;
 }
 
@@ -1141,7 +1066,7 @@ enum com_check_flags {
        CHECK_PLAYLISTS = 4
 };
 
-int com_check(int fd, int argc, char * const * const argv)
+int com_check(struct rc4_context *rc4c, int argc, char * const * const argv)
 {
        unsigned flags = 0;
        int i, ret;
@@ -1173,17 +1098,20 @@ int com_check(int fd, int argc, char * const * const argv)
        if (!flags)
                flags = ~0U;
        if (flags & CHECK_AFT) {
-               ret = send_callback_request(aft_check_callback, NULL, send_result, &fd);
+               ret = send_callback_request(aft_check_callback, NULL,
+                       rc4_send_result, rc4c);
                if (ret < 0)
                        return ret;
        }
        if (flags & CHECK_PLAYLISTS) {
-               ret = send_callback_request(playlist_check_callback, NULL, send_result, &fd);
+               ret = send_callback_request(playlist_check_callback,
+                       NULL, rc4_send_result, rc4c);
                if (ret < 0)
                        return ret;
        }
        if (flags & CHECK_MOODS) {
-               ret = send_callback_request(mood_check_callback, NULL, send_result, &fd);
+               ret = send_callback_request(mood_check_callback, NULL,
+                       rc4_send_result, rc4c);
                if (ret < 0)
                        return ret;
        }
@@ -1216,12 +1144,30 @@ void afs_event(enum afs_events event, struct para_buffer *pb,
        }
 }
 
+/**
+ * Dummy event handler for the images table.
+ *
+ * \param event Unused.
+ * \param pb Unused.
+ * \param data Unused.
+ *
+ * This table does not honor events.
+ */
 int images_event_handler(__a_unused enum afs_events event,
        __a_unused  struct para_buffer *pb, __a_unused void *data)
 {
        return 1;
 }
 
+/**
+ * Dummy event handler for the lyrics table.
+ *
+ * \param event Unused.
+ * \param pb Unused.
+ * \param data Unused.
+ *
+ * This table does not honor events.
+ */
 int lyrics_event_handler(__a_unused enum afs_events event,
        __a_unused struct para_buffer *pb, __a_unused void *data)
 {
diff --git a/afs.cmd b/afs.cmd
index 0af4511..f055199 100644 (file)
--- a/afs.cmd
+++ b/afs.cmd
@@ -3,7 +3,8 @@ SF: afs.c aft.c attribute.c
 HC: Prototypes for the commands of the audio file selector.
 CC: Array of commands for the audio file selector.
 AT: server_command
-IN: para error string afh afs server list user_list
+SI: openssl/rc4 osl regex
+IN: para error crypt command string afh afs server list user_list
 SN: list of afs commands
 TM: mood lyr img pl
 ---
@@ -54,6 +55,8 @@ H:            -ll:   long listing mode (equivalent to -l)
 H:
 H:             -lv:   verbose listing mode
 H:
+H:             -lp:   parser-friendly mode
+H:
 H:             -lm:   mbox listing mode
 H:
 H:             -lc:   chunk-table listing mode
@@ -94,7 +97,7 @@ H:            -sa:  sort by audio format.
 ---
 N: lsatt
 P: AFS_READ
-D: List attributes
+D: List attributes.
 U: lsatt [-i] [-l] [-r] [pattern]
 H: Print the list of all defined attributes which match the
 H: given pattern. If no pattern is given, the full list is
@@ -184,7 +187,7 @@ H:  a slash (see fnmatch(3)).
 N: touch
 P: AFS_READ | AFS_WRITE
 D: Manipulate the afs data for all audio files matching a pattern.
-U: touch [-n numplayed] [-l lastplayed] [-y lyrics_id] [-i image_id] [-a amp] [-v] [-p] pattern
+U: touch [-n=numplayed] [-l=lastplayed] [-y=lyrics_id] [-i=image_id] [-a=amp] [-v] [-p] pattern
 H: If no option is given, lastplayed is set to the current time
 H: and numplayed is increased by one. Otherwise, only the given
 H: options are taken into account.
@@ -197,7 +200,7 @@ H:
 H: -l  Set lastplayed time. The last time this audio file was selected.
 H:     Must be given as the number of seconds since the epoch. Example:
 H:
-H:             touch -l $(date +%s) file
+H:             touch -l=$(date +%s) file
 H:
 H:     sets the lastplayed time of 'file' to the current time.
 H:
@@ -257,7 +260,7 @@ H: loads the mood named 'foo'.
 ---
 T: add
 N: add@member@
-O: int com_add@member@(int fd, int argc, char * const * const argv);
+O: int com_add@member@(struct rc4_context *rc4c, int argc, char * const * const argv);
 P: AFS_READ | AFS_WRITE
 D: Read data from stdin and add it as a blob to the @member@ table.
 U: add@member@ @member@_name
@@ -270,7 +273,7 @@ H: given name already exists, its contents are replaced by the new data.
 ---
 T: cat
 N: cat@member@
-O: int com_cat@member@(int fd, int argc, char * const * const argv);
+O: int com_cat@member@(struct rc4_context *rc4c, int argc, char * const * const argv);
 P: AFS_READ
 D: Dump the contents of a blob of type @member@ to stdout.
 U: cat@member@ @member@_name
@@ -280,7 +283,7 @@ H: they were previously added.
 ---
 T: ls
 N: ls@member@
-O: int com_ls@member@(int fd, int argc, char * const * const argv);
+O: int com_ls@member@(struct rc4_context *rc4c, int argc, char * const * const argv);
 P: AFS_READ
 D: List blobs of type @member@ matching a pattern.
 U: ls@member@ [-i] [-l] [-r] [pattern]
@@ -300,7 +303,7 @@ H: -r       Reverse sort order.
 ---
 T: rm
 N: rm@member@
-O: int com_rm@member@(int fd, int argc, char * const * const argv);
+O: int com_rm@member@(struct rc4_context *rc4c, int argc, char * const * const argv);
 P: AFS_READ | AFS_WRITE
 D: Remove blob(s) of type @member@ from the @member@ table.
 U: rm@member@ pattern...
@@ -309,7 +312,7 @@ H: any given pattern.
 ---
 T: mv
 N: mv@member@
-O: int com_mv@member@(int fd, int argc, char * const * const argv);
+O: int com_mv@member@(struct rc4_context *rc4c, int argc, char * const * const argv);
 P: AFS_READ | AFS_WRITE
 D: Rename a blob of type @member@.
 U: mv@member@ old_@member@_name new_@member@_name
diff --git a/afs.h b/afs.h
index bcc3630..76b8190 100644 (file)
--- a/afs.h
+++ b/afs.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007-2009 Andre Noll <maan@systemlinux.org>
+ * Copyright (C) 2007-2010 Andre Noll <maan@systemlinux.org>
  *
  * Licensed under the GPL v2. For licencing details see COPYING.
  */
@@ -7,7 +7,6 @@
 /** \file afs.h Exported symbols of the audio file selector. */
 
 #include <regex.h>
-#include "osl.h"
 #include "hash.h"
 
 /** Audio file selector data stored in the audio file table. */
@@ -98,7 +97,13 @@ struct afs_table {
                void *data);
 };
 
-enum play_mode {PLAY_MODE_MOOD, PLAY_MODE_PLAYLIST};
+/** How audio files are selected by afs. */
+enum play_mode {
+       /** Admissible files are determined by a mood definition. */
+       PLAY_MODE_MOOD,
+       /** All listed files are admissible. */
+       PLAY_MODE_PLAYLIST,
+};
 
 /**
  * Data about one audio file.
@@ -118,25 +123,33 @@ struct ls_data {
        HASH_TYPE *hash;
 };
 
-void make_empty_status_items(char *buf);
-
-/** At most that many bytes will be passed from afs to para_server. */
-#define VERBOSE_LS_OUTPUT_SIZE 4096
-
 /** Data about the current audio file, passed from afs to server. */
 struct audio_file_data {
-       /** Same info as ls -lv -p current audio_file. */
-       char verbose_ls_output[VERBOSE_LS_OUTPUT_SIZE];
        /** The open file descriptor to the current audio file. */
        int fd;
        /** Vss needs this for streaming. */
        struct afh_info afhi;
 };
 
+/**
+ * Codes used for communication between the server and the afs process.
+ *
+ * Before forking the afs child, para_server creates a bidirectional pipe
+ * through which both processes communicate. Usually para_server requests a new
+ * audio in order to start streaming or when the end of the current audio file
+ * has been reached.  The afs process responds to such a request by sending
+ * back an eight byte buffer. The first four bytes is the uint32_t
+ * representation of the code, usually \p NEXT_AUDIO_FILE if an admissible
+ * audio file was found, successfully opened and verified. The other four bytes
+ * represent the shared memory id of the shared memory area that contains
+ * details about the audio file to be streamed next. The open file descriptor
+ * of that file is also passed from afs to para_server through the same pipe.
+ */
 enum afs_server_code {
+       /** An audio file was successfully opened. */
        NEXT_AUDIO_FILE,
+       /** No admissible audio file was found. */
        NO_ADMISSIBLE_FILES,
-       AFD_CHANGE
 };
 
 /** Flags passed to for_each_matching_row(). */
@@ -188,7 +201,7 @@ typedef void callback_function(int fd, const struct osl_object *);
  * \sa \ref send_callback_request().
  */
 typedef int callback_result_handler(struct osl_object *result, void *private);
-int send_result(struct osl_object *result, void *fd_ptr);
+int rc4_send_result(struct osl_object *result, void *private);
 int pass_buffer_as_shm(char *buf, size_t size, void *fd_ptr);
 
 __noreturn void afs_init(uint32_t cookie, int socket_fd);
@@ -204,9 +217,6 @@ int send_option_arg_callback_request(struct osl_object *options,
 int send_standard_callback_request(int argc,  char * const * const argv,
                callback_function *f, callback_result_handler *result_handler,
                void *private_result_data);
-int stdin_command(int fd, struct osl_object *arg_obj, callback_function *f,
-               unsigned max_len, callback_result_handler *result_handler,
-               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);
 
@@ -245,13 +255,6 @@ int get_afsi_object_of_row(const struct osl_row *row, struct osl_object *obj);
 int audio_file_loop(void *private_data, osl_rbtree_loop_func *func);
 void aft_check_callback(int fd, __a_unused const struct osl_object *query);
 
-/* mood */
-int change_current_mood(char *mood_name);
-void close_current_mood(void);
-int reload_current_mood(void);
-void mood_check_callback(int fd, __a_unused const struct osl_object *query);
-
-
 /* playlist */
 int playlist_open(char *name);
 void playlist_close(void);
@@ -289,21 +292,3 @@ enum blob_table_columns {
        /** A blob table has that many columns. */
        NUM_BLOB_COLUMNS
 };
-
-/** Define an osl table description for a blob table. */
-#define DEFINE_BLOB_TABLE_DESC(table_name) \
-       struct osl_table_description table_name ## _table_desc = { \
-               .name = #table_name, \
-               .num_columns = NUM_BLOB_COLUMNS, \
-               .flags = OSL_LARGE_TABLE, \
-               .column_descriptions = blob_cols \
-       };
-
-/** Define a pointer to an osl blob table with a canonical name. */
-#define DEFINE_BLOB_TABLE_PTR(table_name) struct osl_table *table_name ## _table;
-
-/** Define a blob table. */
-#define INIT_BLOB_TABLE(table_name) \
-       DEFINE_BLOB_TABLE_DESC(table_name); \
-       DEFINE_BLOB_TABLE_PTR(table_name);
-
diff --git a/aft.c b/aft.c
index 5fb0480..25058e7 100644 (file)
--- a/aft.c
+++ b/aft.c
@@ -1,19 +1,23 @@
 /*
- * Copyright (C) 2007-2009 Andre Noll <maan@systemlinux.org>
+ * Copyright (C) 2007-2010 Andre Noll <maan@systemlinux.org>
  *
  * Licensed under the GPL v2. For licencing details see COPYING.
  */
 
 /** \file aft.c Audio file table functions. */
 
+#include <regex.h>
 #include <dirent.h> /* readdir() */
-#include "para.h"
-#include "error.h"
-#include "string.h"
+#include <openssl/rc4.h>
 #include <sys/mman.h>
 #include <fnmatch.h>
 #include <sys/shm.h>
+#include <osl.h>
 
+#include "para.h"
+#include "error.h"
+#include "crypt.h"
+#include "string.h"
 #include "afh.h"
 #include "afs.h"
 #include "net.h"
@@ -23,6 +27,8 @@
 #include "portable_io.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 {
@@ -63,7 +69,9 @@ enum ls_listing_mode {
        /** -lm */
        LS_MODE_MBOX,
        /** -lc */
-       LS_MODE_CHUNKS
+       LS_MODE_CHUNKS,
+       /** -lp */
+       LS_MODE_PARSER,
 };
 
 /** The flags accepted by the ls command. */
@@ -220,12 +228,27 @@ enum audio_file_table_columns {
        NUM_AFT_COLUMNS
 };
 
+/**
+ * Compare two osl objects pointing to hash values.
+ *
+ * \param obj1 Pointer to the first hash object.
+ * \param obj2 Pointer to the second hash object.
+ *
+ * \return The values required for an osl compare function.
+ *
+ * \sa osl_compare_func, uint32_compare().
+ */
+static int aft_hash_compare(const struct osl_object *obj1, const struct osl_object *obj2)
+{
+       return hash_compare((HASH_TYPE *)obj1->data, (HASH_TYPE *)obj2->data);
+}
+
 static struct osl_column_description aft_cols[] = {
        [AFTCOL_HASH] = {
                .storage_type = OSL_MAPPED_STORAGE,
                .storage_flags = OSL_RBTREE | OSL_FIXED_SIZE | OSL_UNIQUE,
                .name = "hash",
-               .compare_function = osl_hash_compare,
+               .compare_function = aft_hash_compare,
                .data_size = HASH_SIZE
        },
        [AFTCOL_PATH] = {
@@ -338,23 +361,29 @@ enum afhi_offsets {
        CHUNK_TV_TV_USEC_OFFSET = 36,
        /** Number of channels is stored here. (1 byte) */
        AFHI_CHANNELS_OFFSET = 40,
-       /** EOF timeout in ms. (2 byte) */
-       AFHI_EOF_OFFSET = 41,
        /** The tag info position. */
-       AFHI_INFO_STRING_OFFSET = 43,
+       AFHI_INFO_STRING_OFFSET = 41,
        /** Minimal on-disk size of a valid afhi struct. */
-       MIN_AFHI_SIZE = 44
+       MIN_AFHI_SIZE = 47, /* at least 6 null bytes for techinfo/tags */
 };
 
 static unsigned sizeof_afhi_buf(const struct afh_info *afhi)
 {
        if (!afhi)
                return 0;
-       return strlen(afhi->info_string) + MIN_AFHI_SIZE;
+       return MIN_AFHI_SIZE
+               + strlen(afhi->techinfo)
+               + strlen(afhi->tags.artist)
+               + strlen(afhi->tags.title)
+               + strlen(afhi->tags.year)
+               + strlen(afhi->tags.album)
+               + strlen(afhi->tags.comment);
 }
 
 static void save_afhi(struct afh_info *afhi, char *buf)
 {
+       char *p;
+
        if (!afhi)
                return;
        write_u32(buf + AFHI_SECONDS_TOTAL_OFFSET, afhi->seconds_total);
@@ -368,8 +397,14 @@ static void save_afhi(struct afh_info *afhi, char *buf)
        write_u32(buf + HEADER_OFFSET_OFFSET, afhi->header_offset);
        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);
-       write_u16(buf + AFHI_EOF_OFFSET, tv2ms(&afhi->eof_tv));
-       strcpy(buf + AFHI_INFO_STRING_OFFSET, afhi->info_string); /* OK */
+       p = buf + AFHI_INFO_STRING_OFFSET;
+       /* The sprintf's below are OK as our caller 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;
+       p += sprintf(p, "%s", afhi->tags.year) + 1;
+       p += sprintf(p, "%s", afhi->tags.album) + 1;
+       sprintf(p, "%s", afhi->tags.comment);
 }
 
 static void load_afhi(const char *buf, struct afh_info *afhi)
@@ -385,8 +420,12 @@ static void load_afhi(const char *buf, struct afh_info *afhi)
        afhi->header_offset = read_u32(buf + HEADER_OFFSET_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);
-       ms2tv(read_u16(buf + AFHI_EOF_OFFSET), &afhi->eof_tv);
-       afhi->info_string = para_strdup(buf + AFHI_INFO_STRING_OFFSET);
+       afhi->techinfo = (char *)buf + AFHI_INFO_STRING_OFFSET;
+       afhi->tags.artist = afhi->techinfo + strlen(afhi->techinfo) + 1;
+       afhi->tags.title = afhi->tags.artist + strlen(afhi->tags.artist) + 1;
+       afhi->tags.year = afhi->tags.title + strlen(afhi->tags.title) + 1;
+       afhi->tags.album = afhi->tags.year + strlen(afhi->tags.year) + 1;
+       afhi->tags.comment = afhi->tags.album + strlen(afhi->tags.album) + 1;
 }
 
 static unsigned sizeof_chunk_table(struct afh_info *afhi)
@@ -419,27 +458,27 @@ static void load_chunk_table(struct afh_info *afhi, char *buf)
  * \param path The full path of the audio file.
  * \param row Result pointer.
  *
- * \return The return value of the underlying call to osl_get_row().
+ * \return Standard.
  */
 int aft_get_row_of_path(const char *path, struct osl_row **row)
 {
        struct osl_object obj = {.data = (char *)path, .size = strlen(path) + 1};
 
-       return osl_get_row(audio_file_table, AFTCOL_PATH, &obj, row);
+       return osl(osl_get_row(audio_file_table, AFTCOL_PATH, &obj, row));
 }
 
 /**
  * Get the row of the audio file table corresponding to the given hash value.
  *
  * \param hash The hash value of the desired audio file.
- * \param row resul pointer.
+ * \param row Result pointer.
  *
- * \return The return value of the underlying call to osl_get_row().
+ * \return Standard.
  */
-int aft_get_row_of_hash(HASH_TYPE *hash, struct osl_row **row)
+static int aft_get_row_of_hash(HASH_TYPE *hash, struct osl_row **row)
 {
        const struct osl_object obj = {.data = hash, .size = HASH_SIZE};
-       return osl_get_row(audio_file_table, AFTCOL_HASH, &obj, row);
+       return osl(osl_get_row(audio_file_table, AFTCOL_HASH, &obj, row));
 }
 
 /**
@@ -448,11 +487,11 @@ int aft_get_row_of_hash(HASH_TYPE *hash, struct osl_row **row)
  * \param row Pointer to a row in the audio file table.
  * \param obj Result pointer.
  *
- * \return The return value of the underlying call to osl_get_object().
+ * \return Standard.
  */
 int get_afsi_object_of_row(const struct osl_row *row, struct osl_object *obj)
 {
-       return osl_get_object(audio_file_table, row, AFTCOL_AFSI, obj);
+       return osl(osl_get_object(audio_file_table, row, AFTCOL_AFSI, obj));
 }
 
 /**
@@ -464,7 +503,7 @@ int get_afsi_object_of_row(const struct osl_row *row, struct osl_object *obj)
  *
  * \return Positive on success, negative on errors.
  */
-int get_afsi_object_of_path(const char *path, struct osl_object *obj)
+static int get_afsi_object_of_path(const char *path, struct osl_object *obj)
 {
        struct osl_row *row;
        int ret = aft_get_row_of_path(path, &row);
@@ -521,8 +560,8 @@ int get_afsi_of_path(const char *path, struct afs_info *afsi)
 int get_audio_file_path_of_row(const struct osl_row *row, char **path)
 {
        struct osl_object path_obj;
-       int ret = osl_get_object(audio_file_table, row, AFTCOL_PATH,
-               &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;
@@ -539,9 +578,10 @@ int get_audio_file_path_of_row(const struct osl_row *row, char **path)
  *
  * \sa get_hash_of_row().
  */
-static int get_hash_object_of_aft_row(const struct osl_row *row, struct osl_object *obj)
+static int get_hash_object_of_aft_row(const struct osl_row *row,
+               struct osl_object *obj)
 {
-       return osl_get_object(audio_file_table, row, AFTCOL_HASH, obj);
+       return osl(osl_get_object(audio_file_table, row, AFTCOL_HASH, obj));
 }
 
 /**
@@ -579,8 +619,8 @@ static int get_hash_of_row(const struct osl_row *row, HASH_TYPE **hash)
 int get_afhi_of_row(const struct osl_row *row, struct afh_info *afhi)
 {
        struct osl_object obj;
-       int ret = osl_get_object(audio_file_table, row, AFTCOL_AFHI,
-               &obj);
+       int ret = osl(osl_get_object(audio_file_table, row, AFTCOL_AFHI,
+               &obj));
        if (ret < 0)
                return ret;
        load_afhi(obj.data, afhi);
@@ -612,6 +652,18 @@ err:
        return ret;
 }
 
+/**
+ * Extract a afd stored in a shared memory area.
+ *
+ * Attach the shared memory area given by \a shmid, load the audio file data
+ * stored therein and detach the area afterwards.  Called by vss, after
+ * receiving a positive response to the request for the next audio file.
+ +
+ * \param shmid The identifier of the shared memory area containing the afd.
+ * \param afd Result pointer.
+ *
+ * \return Standard.
+ */
 int load_afd(int shmid, struct audio_file_data *afd)
 {
        void *shm_afd;
@@ -691,56 +743,68 @@ static void get_duration_buf(int seconds, char *buf, struct ls_options *opts)
        }
 }
 
-static char *make_attribute_lines(const char *att_bitmap, struct afs_info *afsi)
+
+static int write_attribute_items(struct para_buffer *b,
+               const char *att_bitmap, struct afs_info *afsi)
 {
-       char *att_text, *att_lines;
+       char *att_text;
+       int ret;
 
-       get_attribute_text(&afsi->attributes, " ", &att_text);
-       if (!att_text)
-               return para_strdup(att_bitmap);
-       att_lines = make_message("%s: %s\n%s: %s",
-               status_item_list[SI_ATTRIBUTES_BITMAP], att_bitmap,
-               status_item_list[SI_ATTRIBUTES_TXT], att_text);
+       ret = WRITE_STATUS_ITEM(b, SI_ATTRIBUTES_BITMAP, "%s\n", att_bitmap);
+       if (ret < 0)
+               return ret;
+       ret = get_attribute_text(&afsi->attributes, " ", &att_text);
+       if (ret < 0)
+               return ret;
+       ret = WRITE_STATUS_ITEM(b, SI_ATTRIBUTES_TXT, "%s\n", att_text);
        free(att_text);
-       return att_lines;
+       return ret;
 }
 
-static char *make_lyrics_lines(struct afs_info *afsi)
+static int write_lyrics_items(struct para_buffer *b, struct afs_info *afsi)
 {
        char *lyrics_name;
+       int ret;
 
+       ret = WRITE_STATUS_ITEM(b, SI_LYRICS_ID, "%u\n", afsi->lyrics_id);
+       if (ret < 0)
+               return ret;
        lyr_get_name_by_id(afsi->lyrics_id, &lyrics_name);
-       return make_message("%s: %u\n%s: %s\n",
-               status_item_list[SI_LYRICS_ID], afsi->lyrics_id,
-               status_item_list[SI_LYRICS_NAME], lyrics_name?
-                       lyrics_name : "(none)");
+       return WRITE_STATUS_ITEM(b, SI_LYRICS_NAME, "%s\n", lyrics_name?
+               lyrics_name : "(none)");
 }
 
-static char *make_image_lines(struct afs_info *afsi)
+static int write_image_items(struct para_buffer *b, struct afs_info *afsi)
 {
        char *image_name;
+       int ret;
+
+       ret = WRITE_STATUS_ITEM(b, SI_IMAGE_ID, "%u\n", afsi->image_id);
+       if (ret < 0)
+               return ret;
        img_get_name_by_id(afsi->image_id, &image_name);
-       return make_message("%s: %u\n%s: %s\n",
-               status_item_list[SI_IMAGE_ID], afsi->image_id,
-               status_item_list[SI_IMAGE_NAME], image_name?
-                       image_name : "(none)");
+       return WRITE_STATUS_ITEM(b, SI_IMAGE_NAME, "%s\n", image_name?
+               image_name : "(none)");
 }
 
-static char *make_filename_lines(const char *path, unsigned flags)
+static int write_filename_items(struct para_buffer *b, const char *path,
+               unsigned flags)
 {
-       char *dirname, *ret;
-       const char *basename;
+       char *val;
+       int ret;
 
        if (!(flags & LS_FLAG_FULL_PATH))
-               return make_message("%s: %s\n",
-                       status_item_list[SI_BASENAME], path);
-       basename = para_basename(path),
-       dirname = para_dirname(path);
-       ret = make_message("%s: %s\n%s: %s\n%s: %s\n",
-               status_item_list[SI_PATH], path,
-               status_item_list[SI_DIRECTORY], dirname? dirname : "?",
-               status_item_list[SI_BASENAME], basename? basename : "?");
-       free(dirname);
+               return WRITE_STATUS_ITEM(b, SI_BASENAME, "%s\n", path);
+       ret = WRITE_STATUS_ITEM(b, SI_PATH, "%s\n", path);
+       if (ret < 0)
+               return ret;
+       val = para_basename(path);
+       ret = WRITE_STATUS_ITEM(b, SI_BASENAME, "%s\n", val? val : "");
+       if (ret < 0)
+               return ret;
+       val = para_dirname(path);
+       ret = WRITE_STATUS_ITEM(b, SI_DIRECTORY, "%s\n", val? val : "");
+       free(val);
        return ret;
 }
 
@@ -778,6 +842,14 @@ out:
        return ret;
 }
 
+static int write_score(struct para_buffer *b, struct ls_data *d,
+               struct ls_options *opts)
+{
+       if (!(opts->flags & LS_FLAG_ADMISSIBLE_ONLY)) /* no score*/
+               return 0;
+       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)
 {
@@ -785,13 +857,9 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts,
        char att_buf[65];
        char last_played_time[30];
        char duration_buf[30]; /* nobody has an audio file long enough to overflow this */
-       char score_buf[30] = "";
        struct afs_info *afsi = &d->afsi;
        struct afh_info *afhi = &d->afhi;
-       struct ls_widths *w = &opts->widths;
-       int have_score = opts->flags & LS_FLAG_ADMISSIBLE_ONLY;
        char asc_hash[2 * HASH_SIZE + 1];
-       char *att_lines, *lyrics_lines, *image_lines, *filename_lines;
 
        if (opts->mode == LS_MODE_SHORT) {
                ret = para_printf(b, "%s\n", d->path);
@@ -812,16 +880,15 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts,
                        goto out;
        }
        get_duration_buf(afhi->seconds_total, duration_buf, opts);
-       if (have_score) {
-               if (opts->mode == LS_MODE_LONG)
-                       sprintf(score_buf, "%*li ", w->score_width, d->score);
-               else
-                       sprintf(score_buf, "%li ", d->score);
-       }
-
        if (opts->mode == LS_MODE_LONG) {
+               struct ls_widths *w = &opts->widths;
+               if (opts->flags & LS_FLAG_ADMISSIBLE_ONLY) {
+                       ret = para_printf(b, "%*li ",
+                               opts->widths.score_width, d->score);
+                       if (ret < 0)
+                               goto out;
+               }
                ret = para_printf(b,
-                       "%s"    /* score */
                        "%s "   /* attributes */
                        "%*u "  /* amp */
                        "%*d "  /* image_id  */
@@ -834,7 +901,6 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts,
                        "%*d "  /* num_played */
                        "%s "   /* last_played */
                        "%s\n", /* path */
-                       score_buf,
                        att_buf,
                        w->amp_width, afsi->amp,
                        w->image_id_width, afsi->image_id,
@@ -850,11 +916,6 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts,
                );
                goto out;
        }
-       hash_to_asc(d->hash, asc_hash);
-       att_lines = make_attribute_lines(att_buf, afsi);
-       lyrics_lines = make_lyrics_lines(afsi);
-       image_lines = make_image_lines(afsi);
-       filename_lines = make_filename_lines(d->path, opts->flags);
        if (opts->mode == LS_MODE_MBOX) {
                const char *bn = para_basename(d->path);
                ret = para_printf(b,
@@ -866,47 +927,78 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts,
                if (ret < 0)
                        goto out;
        }
-       ret = para_printf(b,
-               "%s" /* filename stuff */
-               "%s%s%s%s" /* score */
-               "%s\n" /* attributes */
-               "%s: %s\n" /* hash */
-               "%s" /* image id, image name */
-               "%s" /* lyrics */
-               "%s: %dkbit/s\n" /* bitrate */
-               "%s: %s\n" /* format */
-               "%s: %dHz\n" /* frequency */
-               "%s: %d\n" /* channels */
-               "%s: %s\n" /* duration */
-               "%s: %lu\n" /* seconds total */
-               "%s: %s\n" /* last played time */
-               "%s: %d\n" /* num_played */
-               "%s: %u\n" /* ampplification */
-               "%s" /* tag info */
-               "%s: %lu\n" /* chunk time */
-               "%s: %lu\n", /* num chunks */
-               filename_lines,
-               have_score? status_item_list[SI_SCORE] : "",
-                       have_score? ": " : "",
-                       score_buf,
-                       have_score? "\n" : "",
-               att_lines,
-               status_item_list[SI_HASH], asc_hash,
-               image_lines,
-               lyrics_lines,
-               status_item_list[SI_BITRATE], afhi->bitrate,
-               status_item_list[SI_FORMAT], audio_format_name(afsi->audio_format_id),
-               status_item_list[SI_FREQUENCY], afhi->frequency,
-               status_item_list[SI_CHANNELS], afhi->channels,
-               status_item_list[SI_DURATION], duration_buf,
-               status_item_list[SI_SECONDS_TOTAL], afhi->seconds_total,
-               status_item_list[SI_LAST_PLAYED], last_played_time,
-               status_item_list[SI_NUM_PLAYED], afsi->num_played,
-               status_item_list[SI_AMPLIFICATION], afsi->amp,
-               afhi->info_string,
-               status_item_list[SI_CHUNK_TIME], tv2ms(&afhi->chunk_tv),
-               status_item_list[SI_NUM_CHUNKS], afhi->chunks_total
-       );
+       ret = write_filename_items(b, d->path, opts->flags);
+       if (ret < 0)
+               goto out;
+       ret = write_score(b, d, opts);
+       if (ret < 0)
+               goto out;
+       ret = write_attribute_items(b, att_buf, afsi);
+       if (ret < 0)
+               goto out;
+       ret = write_image_items(b, afsi);
+       if (ret < 0)
+               goto out;
+       ret = write_lyrics_items(b, afsi);
+       if (ret < 0)
+               goto out;
+       hash_to_asc(d->hash, asc_hash);
+       ret = WRITE_STATUS_ITEM(b, SI_HASH, "%s\n", asc_hash);
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_BITRATE, "%dkbit/s\n", afhi->bitrate);
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_FORMAT, "%s\n",
+               audio_format_name(afsi->audio_format_id));
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_FREQUENCY, "%dHz\n", afhi->frequency);
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_CHANNELS, "%d\n", afhi->channels);
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_DURATION, "%s\n", duration_buf);
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_SECONDS_TOTAL, "%lu\n",
+               afhi->seconds_total);
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_LAST_PLAYED, "%s\n", last_played_time);
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_NUM_PLAYED, "%d\n", afsi->num_played);
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_AMPLIFICATION, "%u\n", afsi->amp);
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_CHUNK_TIME, "%lu\n",
+               tv2ms(&afhi->chunk_tv));
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_NUM_CHUNKS, "%lu\n",
+               afhi->chunks_total);
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_TECHINFO, "%s\n", afhi->techinfo);
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_ARTIST, "%s\n", afhi->tags.artist);
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_TITLE, "%s\n", afhi->tags.title);
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_YEAR, "%s\n", afhi->tags.year);
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_ALBUM, "%s\n", afhi->tags.album);
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_COMMENT, "%s\n", afhi->tags.comment);
        if (ret < 0)
                goto out;
        if (opts->mode == LS_MODE_MBOX) {
@@ -918,129 +1010,10 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts,
                        osl_close_disk_object(&lyrics_def);
                }
        }
-       free(att_lines);
-       free(lyrics_lines);
-       free(image_lines);
-       free(filename_lines);
 out:
-       free(afhi->info_string);
        return ret;
 }
 
-/**
- * Write a list of audio-file related status items with empty values.
- *
- * \param buf Result pointer.
- *
- * This is used by vss when currently no audio file is open.
- */
-void make_empty_status_items(char *buf)
-{
-       sprintf(buf,
-               "%s: \n" /* path */
-               "%s: \n" /* dirname */
-               "%s: \n" /* basename */
-               "%s: \n" /* score */
-               "%s: \n" /* attributes bitmap */
-               "%s: \n" /* attributes txt */
-               "%s: \n" /* hash */
-               "%s: \n" /* image id */
-               "%s: \n" /* image name */
-               "%s: \n" /* lyrics id */
-               "%s: \n" /* lyrics name */
-               "%s: \n" /* bitrate */
-               "%s: \n" /* format */
-               "%s: \n" /* frequency */
-               "%s: \n" /* channels */
-               "%s: \n" /* duration */
-               "%s: \n" /* seconds total */
-               "%s: \n" /* num played */
-               "%s: \n" /* last played */
-               "%s: \n" /* audio file info */
-               "%s: \n" /* taginfo1 */
-               "%s: \n" /* taginfo2 */
-               "%s: \n" /* amplification */
-               ,
-               status_item_list[SI_PATH],
-               status_item_list[SI_DIRECTORY],
-               status_item_list[SI_BASENAME],
-               status_item_list[SI_SCORE],
-               status_item_list[SI_ATTRIBUTES_BITMAP],
-               status_item_list[SI_ATTRIBUTES_TXT],
-               status_item_list[SI_HASH],
-               status_item_list[SI_IMAGE_ID],
-               status_item_list[SI_IMAGE_NAME],
-               status_item_list[SI_LYRICS_ID],
-               status_item_list[SI_LYRICS_NAME],
-               status_item_list[SI_BITRATE],
-               status_item_list[SI_FORMAT],
-               status_item_list[SI_FREQUENCY],
-               status_item_list[SI_CHANNELS],
-               status_item_list[SI_DURATION],
-               status_item_list[SI_SECONDS_TOTAL],
-               status_item_list[SI_NUM_PLAYED],
-               status_item_list[SI_LAST_PLAYED],
-               status_item_list[SI_AUDIO_FILE_INFO],
-               status_item_list[SI_TAGINFO1],
-               status_item_list[SI_TAGINFO2],
-               status_item_list[SI_AMPLIFICATION]
-       );
-}
-
-static void fixup_taginfo(char *begin, char *end)
-{
-       char *p = begin;
-
-       for (;;) {
-               p = strchr(p, '\n');
-               if (!p)
-                       break;
-               if (p >= end - 1)
-                       break;
-               *p = ' ';
-               p++;
-       }
-}
-
-/* crap, remove this ASAP. */
-static int fixup_info_string(char *info_string)
-{
-       char *t1, *t2, *end;
-
-       if (strncmp(info_string, "audio_file_info:", 16))
-               return -ERRNO_TO_PARA_ERROR(EINVAL);
-       t1 = strstr(info_string, "\ntaginfo1:");
-       if (!t1)
-               return -ERRNO_TO_PARA_ERROR(EINVAL);
-       t2 = strstr(info_string, "\ntaginfo2: ");
-       if (!t2)
-               return -ERRNO_TO_PARA_ERROR(EINVAL);
-
-       end = t2 + strlen(t2);
-       fixup_taginfo(info_string + 16, t1);
-       fixup_taginfo(t1 + 10, t2);
-       fixup_taginfo(t2 + 10, end);
-
-       if (t1 - info_string < 80 && t2 - t1 < 80 && end - t2 < 80)
-               return 0;
-       if (t1 - info_string >= 80) {
-               memmove(info_string + 80, t1, end - t1);
-               t1 = info_string + 80;
-               t2 -= t1 - info_string - 80;
-               end -= t1 - info_string - 80;
-       }
-       if (t2 - t1 >= 80) {
-               memmove(t1 + 80, t2, end - t2);
-               end -= t2 - t1 - 80;
-               t2 = t1 + 80;
-       }
-       if (end - t2 >= 80) {
-               t2[78] = '\n';
-               t2[79] = '\0';
-       }
-       return 1;
-}
-
 static int make_status_items(struct audio_file_data *afd,
                struct afs_info *afsi, char *path, long score,
                HASH_TYPE *hash)
@@ -1056,26 +1029,28 @@ static int make_status_items(struct audio_file_data *afd,
                .flags = LS_FLAG_FULL_PATH | LS_FLAG_ADMISSIBLE_ONLY,
                .mode = LS_MODE_VERBOSE,
        };
-       struct para_buffer pb = {.max_size = VERBOSE_LS_OUTPUT_SIZE - 1};
+       struct para_buffer pb = {.max_size = SHMMAX - 1};
        time_t current_time;
        int ret;
 
-       ret = fixup_info_string(afd->afhi.info_string);
-       if (ret < 0) {
-               PARA_WARNING_LOG("ignoring invalid tag info\n");
-               afd->afhi.info_string[0] = '\0';
-       } else if (ret)
-               PARA_NOTICE_LOG("truncated overlong tag info\n");
        time(&current_time);
-       ret = print_list_item(&d, &opts, &pb, current_time); /* frees info string */
-       afd->afhi.info_string = NULL;
+       ret = print_list_item(&d, &opts, &pb, current_time);
        if (ret < 0)
-               goto out;
-       strncpy(afd->verbose_ls_output, pb.buf, VERBOSE_LS_OUTPUT_SIZE);
-       afd->verbose_ls_output[VERBOSE_LS_OUTPUT_SIZE - 1] = '\0';
-out:
-       free(pb.buf);
-       return ret;
+               return ret;
+       free(status_items);
+       status_items = pb.buf;
+       memset(&pb, 0, sizeof(pb));
+       pb.max_size = SHMMAX - 1;
+       pb.flags = PBF_SIZE_PREFIX;
+       ret = print_list_item(&d, &opts, &pb, current_time);
+       if (ret < 0) {
+               free(status_items);
+               status_items = NULL;
+               return ret;
+       }
+       free(parser_friendly_status_items);
+       parser_friendly_status_items = pb.buf;
+       return 1;
 }
 
 /**
@@ -1107,6 +1082,7 @@ int open_and_update_audio_file(struct osl_row *aft_row, long score,
        ret = get_audio_file_path_of_row(aft_row, &path);
        if (ret < 0)
                return ret;
+       PARA_NOTICE_LOG("%s\n", path);
        ret = get_afsi_object_of_row(aft_row, &afsi_obj);
        if (ret < 0)
                return ret;
@@ -1147,7 +1123,6 @@ int open_and_update_audio_file(struct osl_row *aft_row, long score,
        ret = save_afd(afd);
 err:
        free(afd->afhi.chunk_table);
-       free(afd->afhi.info_string);
        osl_close_disk_object(&chunk_table_obj);
        return ret;
 }
@@ -1350,7 +1325,6 @@ static int prepare_ls_row(struct osl_row *row, void *ls_opts)
        }
        return 1;
 err:
-       free(d->afhi.info_string);
        return ret;
 }
 
@@ -1359,11 +1333,11 @@ static void com_ls_callback(int fd, const struct osl_object *query)
        struct ls_options *opts = query->data;
        char *p, *pattern_start = (char *)query->data + sizeof(*opts);
        struct para_buffer b = {.max_size = SHMMAX,
+               .flags = (opts->mode == LS_MODE_PARSER)? PBF_SIZE_PREFIX : 0,
                .max_size_handler = pass_buffer_as_shm, .private_data = &fd};
        int i = 0, ret;
        time_t current_time;
 
-
        if (opts->num_patterns) {
                opts->patterns = para_malloc(opts->num_patterns * sizeof(char *));
                for (i = 0, p = pattern_start; i < opts->num_patterns; i++) {
@@ -1375,8 +1349,8 @@ static void com_ls_callback(int fd, const struct osl_object *query)
        if (opts->flags & LS_FLAG_ADMISSIBLE_ONLY)
                ret = admissible_file_loop(opts, prepare_ls_row);
        else
-               ret = osl_rbtree_loop(audio_file_table, AFTCOL_PATH, opts,
-                       prepare_ls_row);
+               ret = osl(osl_rbtree_loop(audio_file_table, AFTCOL_PATH, opts,
+                       prepare_ls_row));
        if (ret < 0)
                goto out;
        if (!opts->num_matching_paths)
@@ -1409,7 +1383,7 @@ out:
 /*
  * TODO: flags -h (sort by hash)
  */
-int com_ls(int fd, int argc, char * const * const argv)
+int com_ls(struct rc4_context *rc4c, int argc, char * const * const argv)
 {
        int i, ret;
        unsigned flags = 0;
@@ -1449,6 +1423,9 @@ int com_ls(int fd, int argc, char * const * const argv)
                        case 'c':
                                mode = LS_MODE_CHUNKS;
                                continue;
+                       case 'p':
+                               mode = LS_MODE_PARSER;
+                               continue;
                        default:
                                return -E_AFT_SYNTAX;
                        }
@@ -1518,7 +1495,7 @@ int com_ls(int fd, int argc, char * const * const argv)
        opts.mode = mode;
        opts.num_patterns = argc - i;
        ret = send_option_arg_callback_request(&query, opts.num_patterns,
-               argv + i, com_ls_callback, send_result, &fd);
+               argv + i, com_ls_callback, rc4_send_result, rc4c);
        return ret;
 }
 
@@ -1528,12 +1505,12 @@ int com_ls(int fd, int argc, char * const * const argv)
  * \param private_data An arbitrary data pointer, passed to \a func.
  * \param func The custom function to be called.
  *
- * \return The return value of the underlying call to osl_rbtree_loop().
+ * \return Standard.
  */
 int audio_file_loop(void *private_data, osl_rbtree_loop_func *func)
 {
-       return osl_rbtree_loop(audio_file_table, AFTCOL_HASH, private_data,
-               func);
+       return osl(osl_rbtree_loop(audio_file_table, AFTCOL_HASH, private_data,
+               func));
 }
 
 static struct osl_row *find_hash_sister(HASH_TYPE *hash)
@@ -1693,7 +1670,7 @@ static void com_add_callback(int fd, const struct osl_object *query)
        PARA_INFO_LOG("request to add %s\n", path);
        hs = find_hash_sister(hash);
        ret = aft_get_row_of_path(path, &pb);
-       if (ret < 0 && ret != -E_RB_KEY_NOT_FOUND)
+       if (ret < 0 && ret != -OSL_ERRNO_TO_PARA_ERROR(E_OSL_RB_KEY_NOT_FOUND))
                goto out;
        if (hs && pb && hs == pb && !(flags & ADD_FLAG_FORCE)) {
                if (flags & ADD_FLAG_VERBOSE)
@@ -1710,23 +1687,23 @@ static void com_add_callback(int fd, const struct osl_object *query)
                                if (ret < 0)
                                        goto out;
                        }
-                       ret = osl_del_row(audio_file_table, pb);
+                       ret = osl(osl_del_row(audio_file_table, pb));
                        if (ret < 0)
                                goto out;
                        pb = NULL;
                }
                /* file rename, update hs' path */
                if (flags & ADD_FLAG_VERBOSE) {
-                       ret = osl_get_object(audio_file_table, hs,
-                               AFTCOL_PATH, &obj);
+                       ret = osl(osl_get_object(audio_file_table, hs,
+                               AFTCOL_PATH, &obj));
                        if (ret < 0)
                                goto out;
                        ret = para_printf(&msg, "renamed from %s\n", (char *)obj.data);
                        if (ret < 0)
                                goto out;
                }
-               ret = osl_update_object(audio_file_table, hs, AFTCOL_PATH,
-                       &objs[AFTCOL_PATH]);
+               ret = osl(osl_update_object(audio_file_table, hs, AFTCOL_PATH,
+                       &objs[AFTCOL_PATH]));
                if (ret < 0)
                        goto out;
                afs_event(AUDIO_FILE_RENAME, &msg, hs);
@@ -1770,12 +1747,12 @@ static void com_add_callback(int fd, const struct osl_object *query)
                        if (ret < 0)
                                goto out;
                }
-               ret = osl_update_object(audio_file_table, row, AFTCOL_AFHI,
-                       &objs[AFTCOL_AFHI]);
+               ret = osl(osl_update_object(audio_file_table, row, AFTCOL_AFHI,
+                       &objs[AFTCOL_AFHI]));
                if (ret < 0)
                        goto out;
-               ret = osl_update_object(audio_file_table, row, AFTCOL_CHUNKS,
-                       &objs[AFTCOL_CHUNKS]);
+               ret = osl(osl_update_object(audio_file_table, row, AFTCOL_CHUNKS,
+                       &objs[AFTCOL_CHUNKS]));
                if (ret < 0)
                        goto out;
                afs_event(AFHI_CHANGE, &msg, row);
@@ -1793,7 +1770,7 @@ static void com_add_callback(int fd, const struct osl_object *query)
        objs[AFTCOL_AFSI].data = &afsi_buf;
        objs[AFTCOL_AFSI].size = AFSI_SIZE;
        save_afsi(&default_afsi, &objs[AFTCOL_AFSI]);
-       ret = osl_add_and_get_row(audio_file_table, objs, &aft_row);
+       ret = osl(osl_add_and_get_row(audio_file_table, objs, &aft_row));
        afs_event(AUDIO_FILE_ADD, &msg, aft_row);
 out:
        if (ret < 0)
@@ -1805,8 +1782,8 @@ out:
 
 /** Used by com_add(). */
 struct private_add_data {
-       /** The socket file descriptor. */
-       int fd;
+       /** The socket file descriptor, including rc4 keys. */
+       struct rc4_context *rc4c;
        /** The given add flags. */
        uint32_t flags;
 };
@@ -1856,12 +1833,13 @@ static int add_one_audio_file(const char *path, void *private_data)
        query.size = strlen(path) + 1;
        ret = send_callback_request(path_brother_callback, &query,
                get_row_pointer_from_result, &pb);
-       if (ret < 0 && ret != -E_RB_KEY_NOT_FOUND)
+       if (ret < 0 && ret != -OSL_ERRNO_TO_PARA_ERROR(E_OSL_RB_KEY_NOT_FOUND))
                goto out_free;
        ret = 1;
        if (pb && (pad->flags & ADD_FLAG_LAZY)) { /* lazy is really cheap */
                if (pad->flags & ADD_FLAG_VERBOSE)
-                       send_ret = send_va_buffer(pad->fd, "lazy-ignore: %s\n", path);
+                       send_ret = rc4_send_va_buffer(pad->rc4c,
+                               "lazy-ignore: %s\n", path);
                goto out_free;
        }
        /* We still want to add this file. Compute its hash. */
@@ -1881,7 +1859,7 @@ static int add_one_audio_file(const char *path, void *private_data)
        ret = 1;
        if (pb && hs && hs == pb && !(pad->flags & ADD_FLAG_FORCE)) {
                if (pad->flags & ADD_FLAG_VERBOSE)
-                       send_ret = send_va_buffer(pad->fd,
+                       send_ret = rc4_send_va_buffer(pad->rc4c,
                                "%s exists, not forcing update\n", path);
                goto out_unmap;
        }
@@ -1899,13 +1877,13 @@ static int add_one_audio_file(const char *path, void *private_data)
        munmap(map.data, map.size);
        close(fd);
        if (pad->flags & ADD_FLAG_VERBOSE) {
-               send_ret = send_va_buffer(pad->fd, "adding %s\n", path);
+               send_ret = rc4_send_va_buffer(pad->rc4c, "adding %s\n", path);
                if (send_ret < 0)
                        goto out_free;
        }
        save_add_callback_buffer(hash, path, afhi_ptr, pad->flags, format_num, &obj);
        /* Ask afs to consider this entry for adding. */
-       ret = send_callback_request(com_add_callback, &obj, send_result, &pad->fd);
+       ret = send_callback_request(com_add_callback, &obj, rc4_send_result, pad->rc4c);
        goto out_free;
 
 out_unmap:
@@ -1913,21 +1891,26 @@ out_unmap:
        munmap(map.data, map.size);
 out_free:
        if (ret < 0 && send_ret >= 0)
-               send_ret = send_va_buffer(pad->fd, "failed to add %s (%s)\n", path,
-                       para_strerror(-ret));
+               send_ret = rc4_send_va_buffer(pad->rc4c,
+                       "failed to add %s (%s)\n", path, para_strerror(-ret));
        free(obj.data);
        if (afhi_ptr) {
                free(afhi_ptr->chunk_table);
-               free(afhi_ptr->info_string);
+               free(afhi_ptr->techinfo);
+               free(afhi_ptr->tags.artist);
+               free(afhi_ptr->tags.title);
+               free(afhi_ptr->tags.year);
+               free(afhi_ptr->tags.album);
+               free(afhi_ptr->tags.comment);
        }
        /* Stop adding files only on send errors. */
        return send_ret;
 }
 
-int com_add(int fd, int argc, char * const * const argv)
+int com_add(struct rc4_context *rc4c, int argc, char * const * const argv)
 {
        int i, ret;
-       struct private_add_data pad = {.fd = fd, .flags = 0};
+       struct private_add_data pad = {.rc4c = rc4c, .flags = 0};
        struct stat statbuf;
 
        for (i = 1; i < argc; i++) {
@@ -1961,7 +1944,7 @@ int com_add(int fd, int argc, char * const * const argv)
                char *path;
                ret = verify_path(argv[i], &path);
                if (ret < 0) {
-                       ret = send_va_buffer(fd, "%s: %s\n", argv[i],
+                       ret = rc4_send_va_buffer(rc4c, "%s: %s\n", argv[i],
                                para_strerror(-ret));
                        if (ret < 0)
                                return ret;
@@ -1969,7 +1952,7 @@ int com_add(int fd, int argc, char * const * const argv)
                }
                ret = stat(path, &statbuf);
                if (ret < 0) {
-                       ret = send_va_buffer(fd, "failed to stat %s (%s)\n", path,
+                       ret = rc4_send_va_buffer(rc4c, "failed to stat %s (%s)\n", path,
                                strerror(errno));
                        free(path);
                        if (ret < 0)
@@ -1982,7 +1965,7 @@ int com_add(int fd, int argc, char * const * const argv)
                else
                        ret = add_one_audio_file(path, &pad);
                if (ret < 0) {
-                       send_va_buffer(fd, "%s: %s\n", path, para_strerror(-ret));
+                       rc4_send_va_buffer(rc4c, "%s: %s\n", path, para_strerror(-ret));
                        free(path);
                        return ret;
                }
@@ -2114,7 +2097,7 @@ static void com_touch_callback(int fd, const struct osl_object *query)
        free(tad.pb.buf);
 }
 
-int com_touch(int fd, int argc, char * const * const argv)
+int com_touch(struct rc4_context *rc4c, int argc, char * const * const argv)
 {
        struct com_touch_options cto = {
                .num_played = -1,
@@ -2135,33 +2118,33 @@ int com_touch(int fd, int argc, char * const * const argv)
                        i++;
                        break;
                }
-               if (!strncmp(arg, "-n", 2)) {
-                       ret = para_atoi32(arg + 2, &cto.num_played);
+               if (!strncmp(arg, "-n=", 3)) {
+                       ret = para_atoi32(arg + 3, &cto.num_played);
                        if (ret < 0)
                                return ret;
                        continue;
                }
-               if (!strncmp(arg, "-l", 2)) {
-                       ret = para_atoi64(arg + 2, &cto.last_played);
+               if (!strncmp(arg, "-l=", 3)) {
+                       ret = para_atoi64(arg + 3, &cto.last_played);
                        if (ret < 0)
                                return ret;
                        continue;
                }
-               if (!strncmp(arg, "-y", 2)) {
-                       ret = para_atoi32(arg + 2, &cto.lyrics_id);
+               if (!strncmp(arg, "-y=", 3)) {
+                       ret = para_atoi32(arg + 3, &cto.lyrics_id);
                        if (ret < 0)
                                return ret;
                        continue;
                }
-               if (!strncmp(arg, "-i", 2)) {
-                       ret = para_atoi32(arg + 2, &cto.image_id);
+               if (!strncmp(arg, "-i=", 3)) {
+                       ret = para_atoi32(arg + 3, &cto.image_id);
                        if (ret < 0)
                                return ret;
                        continue;
                }
-               if (!strncmp(arg, "-a", 2)) {
+               if (!strncmp(arg, "-a=", 3)) {
                        int32_t val;
-                       ret = para_atoi32(arg + 2, &val);
+                       ret = para_atoi32(arg + 3, &val);
                        if (ret < 0)
                                return ret;
                        if (val < 0 || val > 255)
@@ -2182,9 +2165,9 @@ int com_touch(int fd, int argc, char * const * const argv)
        if (i >= argc)
                return -E_AFT_SYNTAX;
        ret = send_option_arg_callback_request(&query, argc - i,
-               argv + i, com_touch_callback, send_result, &fd);
+               argv + i, com_touch_callback, rc4_send_result, rc4c);
        if (ret < 0)
-               send_va_buffer(fd, "%s\n", para_strerror(-ret));
+               rc4_send_va_buffer(rc4c, "%s\n", para_strerror(-ret));
        return ret;
 }
 
@@ -2220,7 +2203,7 @@ static int remove_audio_file(__a_unused struct osl_table *table,
                        return ret;
        }
        afs_event(AUDIO_FILE_REMOVE, &crd->pb, row);
-       ret = osl_del_row(audio_file_table, row);
+       ret = osl(osl_del_row(audio_file_table, row));
        if (ret < 0)
                para_printf(&crd->pb, "%s: %s\n", name, para_strerror(-ret));
        else
@@ -2266,7 +2249,7 @@ static void com_rm_callback(int fd, const struct osl_object *query)
 }
 
 /* TODO options: -r (recursive) */
-int com_rm(int fd, int argc,  char * const * const argv)
+int com_rm(struct rc4_context *rc4c, int argc,  char * const * const argv)
 {
        uint32_t flags = 0;
        struct osl_object query = {.data = &flags, .size = sizeof(flags)};
@@ -2297,9 +2280,9 @@ int com_rm(int fd, int argc,  char * const * const argv)
        if (i >= argc)
                return -E_AFT_SYNTAX;
        ret = send_option_arg_callback_request(&query, argc - i, argv + i,
-               com_rm_callback, send_result, &fd);
+               com_rm_callback, rc4_send_result, rc4c);
        if (ret < 0)
-               send_va_buffer(fd, "%s\n", para_strerror(-ret));
+               rc4_send_va_buffer(rc4c, "%s\n", para_strerror(-ret));
        return ret;
 }
 
@@ -2414,7 +2397,7 @@ out:
        free(cad.pb.buf);
 }
 
-int com_cpsi(int fd, int argc,  char * const * const argv)
+int com_cpsi(struct rc4_context *rc4c, int argc,  char * const * const argv)
 {
        unsigned flags = 0;
        int i, ret;
@@ -2454,17 +2437,48 @@ int com_cpsi(int fd, int argc,  char * const * const argv)
                }
                break;
        }
-       if (i + 1 >= argc) /* need at least souce file and pattern */
+       if (i + 1 >= argc) /* need at least source file and pattern */
                return -E_AFT_SYNTAX;
        if (!(flags & ~CPSI_FLAG_VERBOSE)) /* no copy flags given */
                flags = ~(unsigned)CPSI_FLAG_VERBOSE | flags;
        ret = send_option_arg_callback_request(&options, argc - i, argv + i,
-               com_cpsi_callback, send_result, &fd);
+               com_cpsi_callback, rc4_send_result, rc4c);
        if (ret < 0)
-               send_va_buffer(fd, "%s\n", para_strerror(-ret));
+               rc4_send_va_buffer(rc4c, "%s\n", para_strerror(-ret));
        return ret;
 }
 
+static void afs_stat_callback(int fd, const struct osl_object *query)
+{
+       int *parser_friendly = query->data;
+       char *buf = *parser_friendly?
+               parser_friendly_status_items : status_items;
+
+       if (!buf)
+               return;
+       pass_buffer_as_shm(buf, strlen(buf), &fd);
+}
+
+/**
+ * Get the current afs status items from the afs process and send it using RC4.
+ *
+ * \param rc4c The rc4 context for data encryption.
+ * \param parser_friendly Whether parser-friendly output format should be used.
+ *
+ * As the contents of the afs status items change in time and the command
+ * handler only has a COW version created at fork time, it can not send
+ * up-to-date afs status items directly. Therefore the usual callback mechanism
+ * is used to pass the status items from the afs process to the command handler
+ * via a shared memory area and a pipe.
+ */
+int send_afs_status(struct rc4_context *rc4c, int parser_friendly)
+{
+       struct osl_object query = {.data = &parser_friendly,
+               .size = sizeof(parser_friendly)};
+
+       return send_callback_request(afs_stat_callback, &query, rc4_send_result, rc4c);
+}
+
 /* TODO: optionally fix problems by removing offending rows */
 static int check_audio_file(struct osl_row *row, void *data)
 {
@@ -2543,6 +2557,10 @@ static void aft_close(void)
 {
        osl_close_table(audio_file_table, OSL_MARK_CLEAN);
        audio_file_table = NULL;
+       free(status_items);
+       status_items = NULL;
+       free(parser_friendly_status_items);
+       parser_friendly_status_items = NULL;
 }
 
 /**
@@ -2559,7 +2577,7 @@ static int aft_open(const char *dir)
        int ret;
 
        audio_file_table_desc.dir = dir;
-       ret = osl_open_table(&audio_file_table_desc, &audio_file_table);
+       ret = osl(osl_open_table(&audio_file_table_desc, &audio_file_table));
        if (ret >= 0) {
                unsigned num;
                osl_get_num_rows(audio_file_table, &num);
@@ -2568,7 +2586,7 @@ static int aft_open(const char *dir)
        }
        PARA_INFO_LOG("failed to open audio file table\n");
        audio_file_table = NULL;
-       if (ret >= 0 || is_errno(-ret, ENOENT))
+       if (ret >= 0 || ret == -OSL_ERRNO_TO_PARA_ERROR(E_OSL_NOENT))
                return 1;
        return ret;
 }
@@ -2576,7 +2594,7 @@ static int aft_open(const char *dir)
 static int aft_create(const char *dir)
 {
        audio_file_table_desc.dir = dir;
-       return osl_create_table(&audio_file_table_desc);
+       return osl(osl_create_table(&audio_file_table_desc));
 }
 
 static int clear_attribute(struct osl_row *row, void *data)
@@ -2617,6 +2635,11 @@ static int aft_event_handler(enum afs_events event, struct para_buffer *pb,
        }
 }
 
+/**
+ * Initialize the audio file table.
+ *
+ * \param t Pointer to the structure to be initialized.
+ */
 void aft_init(struct afs_table *t)
 {
        t->open = aft_open;
index 53c9738..7a4b868 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2005-2009 Andre Noll <maan@systemlinux.org>
+ * Copyright (C) 2005-2010 Andre Noll <maan@systemlinux.org>
  *
  * Licensed under the GPL v2. For licencing details see COPYING.
  */
  * based on the vplay program by Michael Beck.
  */
 
+#include <regex.h>
 #include <sys/types.h>
 #include <dirent.h>
 #include <alsa/asoundlib.h>
+#include <sys/time.h>
+#include <stdbool.h>
 
 #include "para.h"
 #include "fd.h"
@@ -22,7 +25,9 @@
 #include "list.h"
 #include "sched.h"
 #include "ggo.h"
+#include "buffer_tree.h"
 #include "write.h"
+#include "write_common.h"
 #include "alsa_write.cmdline.h"
 #include "error.h"
 
@@ -38,7 +43,7 @@ struct private_alsa_write_data {
        /** The approximate maximum buffer duration in us. */
        unsigned buffer_time;
        /* Number of frames that fit into the buffer. */
-       unsigned buffer_frames;
+       snd_pcm_uframes_t buffer_frames;
        /**
         * The samplerate given by command line option or the decoder
         * of the writer node group.
@@ -49,6 +54,7 @@ struct private_alsa_write_data {
         * decoder of the writer node group.
         */
        unsigned channels;
+       struct timeval drain_barrier;
 };
 
 /* Install PCM software and hardware configuration. */
@@ -57,10 +63,11 @@ static int alsa_init(struct private_alsa_write_data *pad,
 {
        snd_pcm_hw_params_t *hwparams;
        snd_pcm_sw_params_t *swparams;
-       snd_pcm_uframes_t buffer_size, start_threshold, stop_threshold;
+       snd_pcm_uframes_t start_threshold, stop_threshold;
        snd_pcm_uframes_t period_size;
        int err;
 
+       PARA_INFO_LOG("opening %s\n", conf->device_arg);
        err = snd_pcm_open(&pad->handle, conf->device_arg,
                SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
        if (err < 0)
@@ -92,22 +99,22 @@ static int alsa_init(struct private_alsa_write_data *pad,
        if (snd_pcm_hw_params(pad->handle, hwparams) < 0)
                return -E_HW_PARAMS;
        snd_pcm_hw_params_get_period_size(hwparams, &period_size, NULL);
-       snd_pcm_hw_params_get_buffer_size(hwparams, &buffer_size);
-       PARA_INFO_LOG("buffer size: %lu, period_size: %lu\n", buffer_size,
+       snd_pcm_hw_params_get_buffer_size(hwparams, &pad->buffer_frames);
+       PARA_INFO_LOG("buffer size: %lu, period_size: %lu\n", pad->buffer_frames,
                period_size);
-       if (period_size == buffer_size)
+       if (period_size == pad->buffer_frames)
                return -E_BAD_PERIOD;
        snd_pcm_sw_params_current(pad->handle, swparams);
        snd_pcm_sw_params_set_avail_min(pad->handle, swparams, period_size);
-       if (buffer_size < 1)
+       if (pad->buffer_frames < 1)
                start_threshold = 1;
        else
-               start_threshold = PARA_MIN(buffer_size,
+               start_threshold = PARA_MIN(pad->buffer_frames,
                        (snd_pcm_uframes_t)pad->samplerate);
        if (snd_pcm_sw_params_set_start_threshold(pad->handle, swparams,
                        start_threshold) < 0)
                return -E_START_THRESHOLD;
-       stop_threshold = buffer_size;
+       stop_threshold = pad->buffer_frames;
        if (snd_pcm_sw_params_set_stop_threshold(pad->handle, swparams,
                        stop_threshold) < 0)
                return -E_STOP_THRESHOLD;
@@ -120,42 +127,31 @@ static int alsa_init(struct private_alsa_write_data *pad,
        PARA_INFO_LOG("bytes per frame: %d\n", pad->bytes_per_frame);
        if (snd_pcm_nonblock(pad->handle, 1))
                PARA_ERROR_LOG("failed to set nonblock mode\n");
-       pad->buffer_frames = 1000 * pad->buffer_time / pad->samplerate;
-       PARA_INFO_LOG("max buffered frames: %d\n", pad->buffer_frames);
        return 1;
 }
 
 /* Open an instance of the alsa writer. */
 static int alsa_open(struct writer_node *wn)
 {
-       struct alsa_write_args_info *conf = wn->conf;
-       struct writer_node_group *wng = wn->wng;
-       struct private_alsa_write_data *pad = para_calloc(sizeof(*pad));
-
-       wn->private_data = pad;
-       if (!conf->samplerate_given && wng->samplerate)
-               pad->samplerate = *wng->samplerate;
-       else
-               pad->samplerate = conf->samplerate_arg;
-       if (!conf->channels_given && wng->channels)
-               pad->channels = *wng->channels;
-       else
-               pad->channels = conf->channels_arg;
-       PARA_INFO_LOG("%d channel(s), %dHz\n", pad->channels, pad->samplerate);
+       wn->private_data = para_calloc(sizeof(struct private_alsa_write_data));
        return 1;
 }
 
-static int alsa_write_pre_select(struct sched *s, struct writer_node *wn)
+static void alsa_write_pre_select(struct sched *s, struct task *t)
 {
+       struct writer_node *wn = container_of(t, struct writer_node, task);
        struct private_alsa_write_data *pad = wn->private_data;
-       struct writer_node_group *wng = wn->wng;
        struct timeval tv;
        snd_pcm_sframes_t avail, underrun;
+       int ret;
 
        if (!pad->handle)
-               return 1;
-       if (*wng->loaded - wn->written < pad->bytes_per_frame)
-               return 1;
+               return;
+       ret = btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_LEAF);
+       if (ret < 0)
+               sched_request_timeout_ms(20, s);
+       if (ret <= 0)
+               return;
        /*
         * Data is available to be written to the alsa handle.  Compute number
         * of milliseconds until next buffer underrun would occur.
@@ -177,49 +173,6 @@ static int alsa_write_pre_select(struct sched *s, struct writer_node *wn)
        ms2tv(underrun, &tv);
        if (tv_diff(&s->timeout, &tv, NULL) > 0)
                s->timeout = tv;
-       return 1;
-}
-
-static int alsa_write_post_select(__a_unused struct sched *s,
-               struct writer_node *wn)
-{
-       struct private_alsa_write_data *pad = wn->private_data;
-       struct writer_node_group *wng = wn->wng;
-       size_t bytes = *wng->loaded - wn->written;
-       unsigned char *data = (unsigned char*)*wng->bufp + wn->written;
-       snd_pcm_sframes_t ret, frames, avail;
-
-       if (*wng->input_error < 0 && (!pad->handle || bytes < pad->bytes_per_frame)) {
-               wn->written = *wng->loaded;
-               return *wng->input_error;
-       }
-       if (!bytes) /* no data available */
-               return 0;
-       if (!pad->handle) {
-               int err = alsa_init(pad, wn->conf);
-               if (err < 0)
-                       return err;
-       }
-       frames = bytes / pad->bytes_per_frame;
-       if (!frames) /* less than a single frame available */
-               return 0;
-       avail = snd_pcm_avail_update(pad->handle);
-       if (avail <= 0)
-               return 0;
-       frames = PARA_MIN(frames, avail);
-       ret = snd_pcm_writei(pad->handle, data, frames);
-       if (ret >= 0) {
-               wn->written += ret * pad->bytes_per_frame;
-               return 1;
-       }
-       PARA_WARNING_LOG("%s\n", snd_strerror(-ret));
-       if (ret == -EPIPE) {
-               snd_pcm_prepare(pad->handle);
-               return 0;
-       }
-       if (ret == -EAGAIN)
-               return 0;
-       return -E_ALSA_WRITE;
 }
 
 static void alsa_close(struct writer_node *wn)
@@ -228,6 +181,11 @@ static void alsa_close(struct writer_node *wn)
        PARA_INFO_LOG("closing writer node %p\n", wn);
 
        if (pad->handle) {
+               /*
+                * It's OK to have a blocking operation here because we already
+                * made sure that the PCM output buffer is (nearly) empty.
+                */
+               snd_pcm_nonblock(pad->handle, 0);
                snd_pcm_drain(pad->handle);
                snd_pcm_close(pad->handle);
                snd_config_update_free_global();
@@ -235,6 +193,84 @@ static void alsa_close(struct writer_node *wn)
        free(pad);
 }
 
+static void alsa_write_post_select(__a_unused struct sched *s,
+               struct task *t)
+{
+       struct writer_node *wn = container_of(t, struct writer_node, task);
+       struct private_alsa_write_data *pad = wn->private_data;
+       struct btr_node *btrn = wn->btrn;
+       char *data;
+       size_t bytes;
+       snd_pcm_sframes_t frames;
+       int ret;
+
+again:
+       t->error = 0;
+       ret = btr_node_status(btrn, wn->min_iqs, BTR_NT_LEAF);
+       if (ret == 0)
+               return;
+       btr_merge(btrn, wn->min_iqs);
+       bytes = btr_next_buffer(btrn, &data);
+       if (ret < 0 || bytes < pad->bytes_per_frame) { /* eof */
+               assert(btr_no_parent(btrn));
+               ret = -E_ALSA_EOF;
+               if (!pad->handle)
+                       goto err;
+               /* wait until pending frames are played */
+               if (pad->drain_barrier.tv_sec == 0) {
+                       PARA_DEBUG_LOG("waiting for device to drain\n");
+                       tv_add(now, &(struct timeval)EMBRACE(0, 200 * 1000),
+                               &pad->drain_barrier);
+                       return;
+               }
+               if (tv_diff(now, &pad->drain_barrier, NULL) > 0)
+                       goto err;
+               return;
+       }
+       if (!pad->handle) {
+               struct alsa_write_args_info *conf = wn->conf;
+               if (bytes == 0) /* no data available */
+                       return;
+               /* defaults */
+               pad->samplerate = conf->samplerate_arg;
+               pad->channels = conf->channels_arg;
+               if (!conf->samplerate_given) { /* config option trumps btr_exec */
+                       int32_t rate;
+                       if (get_btr_samplerate(btrn, &rate) >= 0)
+                               pad->samplerate = rate;
+               }
+               if (!conf->channels_given) {
+                       int32_t ch;
+                       if (get_btr_channels(btrn, &ch) >= 0)
+                               pad->channels = ch;
+               }
+               PARA_INFO_LOG("%d channel(s), %dHz\n", pad->channels, pad->samplerate);
+               ret = alsa_init(pad, wn->conf);
+               if (ret < 0)
+                       goto err;
+               wn->min_iqs = pad->bytes_per_frame;
+       }
+       frames = bytes / pad->bytes_per_frame;
+       frames = snd_pcm_writei(pad->handle, data, frames);
+       if (frames >= 0) {
+               btr_consume(btrn, frames * pad->bytes_per_frame);
+               goto again;
+       }
+       if (frames == -EPIPE) {
+               PARA_WARNING_LOG("underrun (tried to write %zu bytes)\n", bytes);
+               snd_pcm_prepare(pad->handle);
+               return;
+       }
+       if (frames == -EAGAIN)
+               return;
+       PARA_WARNING_LOG("%s\n", snd_strerror(-frames));
+       ret = -E_ALSA_WRITE;
+err:
+       assert(ret < 0);
+       btr_remove_node(btrn);
+       t->error = ret;
+}
+
 __malloc static void *alsa_parse_config(const char *options)
 {
        int ret;
@@ -252,12 +288,17 @@ err_out:
        return NULL;
 }
 
+static void alsa_free_config(void *conf)
+{
+       alsa_cmdline_parser_free(conf);
+}
+
 /**
- * the init function of the alsa writer
+ * The init function of the alsa writer.
  *
- * \param w pointer to the writer to initialize
+ * \param w Pointer to the writer to initialize.
  *
- * \sa struct writer
+ * \sa struct \ref writer.
  */
 void alsa_write_init(struct writer *w)
 {
@@ -270,6 +311,7 @@ void alsa_write_init(struct writer *w)
        w->post_select = alsa_write_post_select;
        w->parse_config = alsa_parse_config;
        w->shutdown = NULL; /* nothing to do */
+       w->free_config = alsa_free_config;
        w->help = (struct ggo_help) {
                .short_help = alsa_write_args_info_help,
                .detailed_help = alsa_write_args_info_detailed_help
index a011c41..29c62e3 100644 (file)
@@ -1,23 +1,24 @@
 /*
- * Copyright (C) 2009 Andre Noll <maan@systemlinux.org>
+ * Copyright (C) 2009-2010 Andre Noll <maan@systemlinux.org>
  *
  * Licensed under the GPL v2. For licencing details see COPYING.
  */
 
 /** \file amp_filter.c Paraslash's amplify filter. */
 
+#include <regex.h>
+#include <stdbool.h>
+
 #include "para.h"
 #include "amp_filter.cmdline.h"
 #include "list.h"
 #include "sched.h"
 #include "ggo.h"
+#include "buffer_tree.h"
 #include "filter.h"
 #include "string.h"
 #include "error.h"
 
-/** The size of the output data buffer. */
-#define AMP_CHUNK_SIZE 40960
-
 extern char *stat_item_values[NUM_STAT_ITEMS];
 
 /** Data specific to the amplify filter. */
@@ -28,27 +29,9 @@ struct private_amp_data {
        unsigned amp;
 };
 
-static ssize_t amp_convert(char *inbuf, size_t inbuf_len, struct filter_node *fn)
-{
-       size_t i, length = PARA_MIN((inbuf_len / 2) * 2,
-               (fn->bufsize - fn->loaded) / 2 * 2);
-       struct private_amp_data *pad = fn->private_data;
-       int16_t *ip = (int16_t *)inbuf, *op = (int16_t *)(fn->buf + fn->loaded);
-
-       if (!length)
-               return 0;
-       for (i = 0; i < length / 2; i++) {
-               int x = (PARA_ABS(*ip) * (64 + pad->amp)) >> 6;
-               *op++ = *ip++ > 0? PARA_MIN(x, 32767) : PARA_MAX(-x, -32768);
-       }
-       fn->loaded += length;
-       return length;
-}
-
 static void amp_close(struct filter_node *fn)
 {
        free(fn->private_data);
-       free(fn->buf);
 }
 
 static int amp_parse_config(int argc, char **argv, void **config)
@@ -61,8 +44,6 @@ static int amp_parse_config(int argc, char **argv, void **config)
        ret = -ERRNO_TO_PARA_ERROR(EINVAL);
        if (amp_conf->amp_arg < 0)
                goto err;
-       PARA_NOTICE_LOG("amplification: %u (scaling factor: %1.2f)\n",
-               amp_conf->amp_arg, amp_conf->amp_arg / 64.0 + 1.0);
        *config = amp_conf;
        return 1;
 err:
@@ -76,14 +57,74 @@ static void amp_open(struct filter_node *fn)
 
        pad->conf = fn->conf;
        fn->private_data = pad;
-       if (!pad->conf->amp_given && stat_item_values[SI_AMPLIFICATION]) {
-               int i = SI_AMPLIFICATION;
-               char *s = stat_item_values[i] + strlen(status_item_list[i]) + 1;
-               sscanf(s, "%u", &pad->amp);
-       } else
+       fn->min_iqs = 2;
+       if (!pad->conf->amp_given && stat_item_values[SI_AMPLIFICATION])
+               sscanf(stat_item_values[SI_AMPLIFICATION], "%u", &pad->amp);
+       else
                pad->amp = pad->conf->amp_arg;
-       fn->bufsize = AMP_CHUNK_SIZE;
-       fn->buf = para_malloc(fn->bufsize);
+       PARA_NOTICE_LOG("amplification: %u (scaling factor: %1.2f)\n",
+               pad->amp, pad->amp / 64.0 + 1.0);
+}
+
+static void amp_post_select(__a_unused struct sched *s, struct task *t)
+{
+       struct filter_node *fn = container_of(t, struct filter_node, task);
+       struct private_amp_data *pad = fn->private_data;
+       struct btr_node *btrn = fn->btrn;
+       int ret, factor = 64 + pad->amp;
+       size_t i, in_bytes, len;
+       int16_t *in, *out;
+       bool inplace = btr_inplace_ok(btrn);
+
+       if (pad->amp == 0) { /* no amplification */
+               t->error = -E_AMP_ZERO_AMP;
+               btr_splice_out_node(btrn);
+               return;
+       }
+next_buffer:
+       ret = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL);
+       if (ret < 0)
+               goto err;
+       if (ret == 0)
+               return;
+       btr_merge(btrn, fn->min_iqs);
+       in_bytes = btr_next_buffer(btrn, (char **)&in);
+       len = in_bytes / 2;
+       if (len == 0) { /* eof and in_bytes == 1 */
+               ret = -E_AMP_EOF;
+               goto err;
+       }
+
+       if (inplace)
+               out = in;
+       else
+               out = para_malloc(len * 2);
+
+       for (i = 0; i < len; i++) {
+               int x = (in[i] * factor) >> 6;
+
+               out[i] = x;
+               if (out[i] != x) /* overflow, clip */
+                       out[i] = (x >= 0)? 32767 : -32768;
+       }
+
+       if (inplace)
+               btr_pushdown_one(btrn);
+       else {
+               btr_consume(btrn, len * 2);
+               btr_add_output((char *)out, len * 2, btrn);
+       }
+       t->error = 0;
+       goto next_buffer;
+err:
+       assert(ret < 0);
+       t->error = ret;
+       btr_remove_node(btrn);
+}
+
+static void amp_free_config(void *conf)
+{
+       amp_cmdline_parser_free(conf);
 }
 
 /**
@@ -98,8 +139,10 @@ void amp_filter_init(struct filter *f)
        amp_cmdline_parser_init(&dummy);
        f->open = amp_open;
        f->close = amp_close;
-       f->convert = amp_convert;
+       f->pre_select = generic_filter_pre_select;
+       f->post_select = amp_post_select;
        f->parse_config = amp_parse_config;
+       f->free_config = amp_free_config;
        f->help = (struct ggo_help) {
                .short_help = amp_filter_args_info_help,
                .detailed_help = amp_filter_args_info_detailed_help
index df66c9d..a353f14 100644 (file)
@@ -1,12 +1,18 @@
 /*
- * Copyright (C) 1997-2009 Andre Noll <maan@systemlinux.org>
+ * Copyright (C) 1997-2010 Andre Noll <maan@systemlinux.org>
  *
  * Licensed under the GPL v2. For licencing details see COPYING.
  */
 
 /** \file attribute.c Attribute handling functions. */
+
+#include <regex.h>
+#include <openssl/rc4.h>
+#include <osl.h>
+
 #include "para.h"
 #include "error.h"
+#include "crypt.h"
 #include "string.h"
 #include "afh.h"
 #include "afs.h"
@@ -89,11 +95,11 @@ int get_attribute_bitnum_by_name(const char *att_name, unsigned char *bitnum)
        struct osl_object obj = {.data = (char *)att_name,
                .size = strlen(att_name) + 1};
        struct osl_row *row;
-       int ret = osl_get_row(attribute_table, ATTCOL_NAME, &obj, &row);
+       int ret = osl(osl_get_row(attribute_table, ATTCOL_NAME, &obj, &row));
 
        if (ret < 0)
                return ret;
-       ret = osl_get_object(attribute_table, row, ATTCOL_BITNUM, &obj);
+       ret = osl(osl_get_object(attribute_table, row, ATTCOL_BITNUM, &obj));
        if (ret < 0)
                return ret;
        *bitnum = *(unsigned char *)obj.data;
@@ -131,7 +137,7 @@ static int print_attribute(struct osl_table *table, struct osl_row *row,
 
        if (!(laad->flags & LSATT_FLAG_LONG))
                return para_printf(&laad->pb, "%s\n", name);
-       ret = osl_get_object(table, row, ATTCOL_BITNUM, &bitnum_obj);
+       ret = osl(osl_get_object(table, row, ATTCOL_BITNUM, &bitnum_obj));
        if (ret < 0) {
                para_printf(&laad->pb, "%s: %s\n", name, para_strerror(-ret));
                return ret;
@@ -171,7 +177,7 @@ static void com_lsatt_callback(int fd, const struct osl_object *query)
        free(laad.pb.buf);
 }
 
-int com_lsatt(int fd, int argc, char * const * const argv)
+int com_lsatt(struct rc4_context *rc4c, int argc, char * const * const argv)
 {
        unsigned flags = 0;
        struct osl_object options = {.data = &flags, .size = sizeof(flags)};
@@ -199,12 +205,12 @@ int com_lsatt(int fd, int argc, char * const * const argv)
                }
        }
        ret = send_option_arg_callback_request(&options, argc - i, argv + i,
-               com_lsatt_callback, send_result, &fd);
+               com_lsatt_callback, rc4_send_result, rc4c);
        if (!ret) {
                if (argc > 1)
-                       ret = send_va_buffer(fd, "no matches\n");
+                       ret = rc4_send_va_buffer(rc4c, "no matches\n");
        } else if (ret < 0)
-               send_va_buffer(fd, "%s\n", para_strerror(-ret));
+               rc4_send_va_buffer(rc4c, "%s\n", para_strerror(-ret));
        return ret;
 }
 
@@ -230,11 +236,11 @@ static void com_setatt_callback(__a_unused int fd, const struct osl_object *quer
                p[len - 1] = '\0';
                obj.data = p;
                obj.size = len + 1;
-               ret = osl_get_row(attribute_table, ATTCOL_NAME, &obj, &row);
+               ret = osl(osl_get_row(attribute_table, ATTCOL_NAME, &obj, &row));
                if (ret < 0)
                        goto out;
-               ret = osl_get_object(attribute_table, row, ATTCOL_BITNUM,
-                       &obj);
+               ret = osl(osl_get_object(attribute_table, row, ATTCOL_BITNUM,
+                       &obj));
                if (ret < 0)
                        goto out;
                if (c == '+')
@@ -272,7 +278,7 @@ out:
                PARA_NOTICE_LOG("%s\n", para_strerror(-ret));
 }
 
-int com_setatt(__a_unused int fd, int argc, char * const * const argv)
+int com_setatt(__a_unused struct rc4_context *rc4c, int argc, char * const * const argv)
 {
        if (argc < 3)
                return -E_ATTR_SYNTAX;
@@ -317,15 +323,15 @@ static void com_addatt_callback(int fd, const struct osl_object *query)
                                goto out;
                        continue;
                }
-               if (ret != -E_RB_KEY_NOT_FOUND) /* error */
+               if (ret != -OSL_ERRNO_TO_PARA_ERROR(E_OSL_RB_KEY_NOT_FOUND)) /* error */
                        goto out;
                objs[ATTCOL_BITNUM].size = 1;
                /* find smallest unused attribute */
                for (bitnum = 0; bitnum < 64; bitnum++) {
                        objs[ATTCOL_BITNUM].data = &bitnum;
-                       ret = osl_get_row(attribute_table, ATTCOL_BITNUM,
-                               &objs[ATTCOL_BITNUM], &row);
-                       if (ret == -E_RB_KEY_NOT_FOUND)
+                       ret = osl(osl_get_row(attribute_table, ATTCOL_BITNUM,
+                               &objs[ATTCOL_BITNUM], &row));
+                       if (ret == -OSL_ERRNO_TO_PARA_ERROR(E_OSL_RB_KEY_NOT_FOUND))
                                break; /* this bitnum is unused, use it */
                        if (ret < 0) /* error */
                                goto out;
@@ -337,7 +343,7 @@ static void com_addatt_callback(int fd, const struct osl_object *query)
                }
                objs[ATTCOL_NAME].data = p;
                objs[ATTCOL_NAME].size = len + 1;
-               ret = osl_add_row(attribute_table, objs);
+               ret = osl(osl_add_row(attribute_table, objs));
                if (ret < 0)
                        goto out;
                aed.name = p;
@@ -353,16 +359,16 @@ out:
        free(pb.buf);
 }
 
-int com_addatt(int fd, int argc, char * const * const argv)
+int com_addatt(struct rc4_context *rc4c, int argc, char * const * const argv)
 {
        int ret;
 
        if (argc < 2)
                return -E_ATTR_SYNTAX;
        ret = send_standard_callback_request(argc - 1, argv + 1, com_addatt_callback,
-               send_result, &fd);
+               rc4_send_result, rc4c);
        if (ret < 0)
-               send_va_buffer(fd, "%s\n", para_strerror(-ret));
+               rc4_send_va_buffer(rc4c, "%s\n", para_strerror(-ret));
        return ret;
 }
 
@@ -380,12 +386,12 @@ static void com_mvatt_callback(int fd, const struct osl_object *query)
        };
        int ret;
 
-       ret = osl_get_row(attribute_table, ATTCOL_NAME, &obj, &row);
+       ret = osl(osl_get_row(attribute_table, ATTCOL_NAME, &obj, &row));
        if (ret < 0)
                goto out;
        obj.data = new;
        obj.size = strlen(new) + 1;
-       ret = osl_update_object(attribute_table, row, ATTCOL_NAME, &obj);
+       ret = osl(osl_update_object(attribute_table, row, ATTCOL_NAME, &obj));
 out:
        if (ret < 0)
                para_printf(&pb, "%s\n", para_strerror(-ret));
@@ -396,16 +402,16 @@ out:
        free(pb.buf);
 }
 
-int com_mvatt(int fd, int argc, char * const * const argv)
+int com_mvatt(struct rc4_context *rc4c, int argc, char * const * const argv)
 {
        int ret;
 
        if (argc != 3)
                return -E_ATTR_SYNTAX;
        ret = send_standard_callback_request(argc - 1, argv + 1, com_mvatt_callback,
-               send_result, &fd);
+               rc4_send_result, rc4c);
        if (ret < 0)
-               send_va_buffer(fd, "%s\n", para_strerror(-ret));
+               rc4_send_va_buffer(rc4c, "%s\n", para_strerror(-ret));
        return ret;
 }
 
@@ -429,7 +435,7 @@ static int remove_attribute(struct osl_table *table, struct osl_row *row,
        ret = get_attribute_bitnum_by_name(name, &red.bitnum);
        if (ret < 0)
                return para_printf(&raad->pb, "%s: %s\n", name, para_strerror(-ret));
-       ret = osl_del_row(table, row);
+       ret = osl(osl_del_row(table, row));
        if (ret < 0)
                return para_printf(&raad->pb, "%s: %s\n", name, para_strerror(-ret));
        ret = para_printf(&raad->pb, "removed attribute %s\n", name);
@@ -468,16 +474,16 @@ static void com_rmatt_callback(int fd, const struct osl_object *query)
        free(raad.pb.buf);
 }
 
-int com_rmatt(int fd, int argc, char * const * const argv)
+int com_rmatt(struct rc4_context *rc4c, int argc, char * const * const argv)
 {
        int ret;
 
        if (argc < 2)
                return -E_ATTR_SYNTAX;
        ret = send_standard_callback_request(argc - 1, argv + 1, com_rmatt_callback,
-               send_result, &fd);
+               rc4_send_result, rc4c);
        if (ret < 0)
-               send_va_buffer(fd, "%s\n", para_strerror(-ret));
+               rc4_send_va_buffer(rc4c, "%s\n", para_strerror(-ret));
        return ret;
 }
 
@@ -533,10 +539,10 @@ int get_attribute_text(uint64_t *atts, const char *delim, char **text)
 
                if (!(*atts & (one << i)))
                        continue;
-               ret = osl_get_row(attribute_table, ATTCOL_BITNUM, &obj, &row);
+               ret = osl(osl_get_row(attribute_table, ATTCOL_BITNUM, &obj, &row));
                if (ret < 0)
                        goto err;
-               ret = osl_get_object(attribute_table, row, ATTCOL_NAME, &obj);
+               ret = osl(osl_get_object(attribute_table, row, ATTCOL_NAME, &obj));
                if (ret < 0)
                        goto err;
                if (*text) {
@@ -559,7 +565,7 @@ err:
  *
  * \sa osl_close_table().
  */
-void attribute_close(void)
+static void attribute_close(void)
 {
        osl_close_table(attribute_table, OSL_MARK_CLEAN);
        attribute_table = NULL;
@@ -579,14 +585,14 @@ static int attribute_open(const char *dir)
        int ret;
 
        attribute_table_desc.dir = dir;
-       ret = osl_open_table(&attribute_table_desc, &attribute_table);
+       ret = osl(osl_open_table(&attribute_table_desc, &attribute_table));
        greatest_att_bitnum = -1; /* no atts available */
        if (ret >= 0) {
                find_greatest_att_bitnum();
                return ret;
        }
        attribute_table = NULL;
-       if (ret >= 0 || is_errno(-ret, ENOENT))
+       if (ret >= 0 || ret == -OSL_ERRNO_TO_PARA_ERROR(E_OSL_NOENT))
                return 1;
        return ret;
 }
@@ -594,7 +600,7 @@ static int attribute_open(const char *dir)
 static int attribute_create(const char *dir)
 {
        attribute_table_desc.dir = dir;
-       return osl_create_table(&attribute_table_desc);
+       return osl(osl_create_table(&attribute_table_desc));
 }
 
 /**
index 5c081da..f91f41f 100644 (file)
--- a/audioc.c
+++ b/audioc.c
@@ -1,11 +1,12 @@
 /*
- * Copyright (C) 2005-2009 Andre Noll <maan@systemlinux.org>
+ * Copyright (C) 2005-2010 Andre Noll <maan@systemlinux.org>
  *
  * Licensed under the GPL v2. For licencing details see COPYING.
  */
 
 /** \file audioc.c the client program used to connect to para_audiod */
 
+#include <regex.h>
 #include <sys/types.h>
 #include <dirent.h>
 
@@ -18,8 +19,8 @@
 
 INIT_AUDIOC_ERRLISTS;
 
-/** the gengetopt structure containing command line args */
-struct audioc_args_info conf;
+/** The gengetopt structure containing command line args. */
+static struct audioc_args_info conf;
 
 static int loglevel;
 INIT_STDERR_LOGGING(loglevel);
@@ -70,7 +71,7 @@ int main(int argc, char *argv[])
 {
        int ret = -E_AUDIOC_SYNTAX, fd;
        char *cf, *buf = NULL, *args;
-       size_t bufsize, loaded = 0;
+       size_t bufsize;
 
        if (audioc_cmdline_parser(argc, argv, &conf))
                goto out;
@@ -92,72 +93,33 @@ int main(int argc, char *argv[])
        args = conf.inputs_num?
                concat_args(conf.inputs_num, conf.inputs) :
                para_strdup("stat");
-       bufsize = conf.bufsize_arg;
-       buf = para_malloc(bufsize);
 
-       if (conf.socket_given) {
+       if (conf.socket_given)
                ret = create_remote_socket(conf.socket_arg);
-       } else {
-               char *hn = para_hostname(),
-                    *socket_name = make_message("/var/paraslash/audiod_socket.%s", hn);
-
+       else {
+               char *hn = para_hostname(), *socket_name = make_message(
+                       "/var/paraslash/audiod_socket.%s", hn);
                ret = create_remote_socket(socket_name);
                free(hn);
                free(socket_name);
        }
-       if (ret < 0)
+       if (ret < 0) {
+               PARA_EMERG_LOG("failed to create remote socket\n");
                goto out;
+       }
        fd = ret;
-       ret = mark_fd_nonblocking(fd);
-       if (ret < 0)
-               goto out;
-       ret = mark_fd_nonblocking(STDOUT_FILENO);
-       if (ret < 0)
-               goto out;
        ret = send_cred_buffer(fd, args);
        if (ret < 0)
                goto out;
-       for (;;) {
-               int max_fileno = -1, check_write = 0;
-               ssize_t len;
-               fd_set rfd, wfd;
-               FD_ZERO(&rfd);
-               FD_ZERO(&wfd);
-               if (loaded < bufsize)
-                       para_fd_set(fd, &rfd, &max_fileno);
-               if (loaded > 0) {
-                       para_fd_set(STDOUT_FILENO, &wfd, &max_fileno);
-                       check_write = 1;
-               }
-               ret = -E_AUDIOC_OVERRUN;
-               if (max_fileno < 0)
-                       goto out;
-               ret = para_select(max_fileno + 1, &rfd, &wfd, NULL);
-               if (ret < 0)
-                       goto out;
-               if (loaded < bufsize && FD_ISSET(fd, &rfd)) {
-                       len = recv_bin_buffer(fd, buf + loaded,
-                               bufsize - loaded);
-                       if (len <= 0) {
-                               ret = len < 0? -E_AUDIOC_READ : 0;
-                               goto out;
-                       }
-                       loaded += len;
-               }
-               if (check_write && FD_ISSET(STDOUT_FILENO, &wfd)) {
-                       ret = write(STDOUT_FILENO, buf, loaded);
-                       if (ret < 0) {
-                               ret = -E_AUDIOC_WRITE;
-                               goto out;
-                       }
-                       loaded -= ret;
-                       if (loaded && ret)
-                               memmove(buf, buf + ret, loaded);
-               }
-       }
+       bufsize = conf.bufsize_arg;
+       buf = para_malloc(bufsize);
+       do {
+               size_t n = ret = recv_bin_buffer(fd, buf, bufsize);
+               if (ret <= 0)
+                       break;
+               ret = write_all(STDOUT_FILENO, buf, &n);
+       } while (ret >= 0);
 out:
-       if (!ret && loaded && buf)
-               ret = write(STDOUT_FILENO, buf, loaded);
        if (ret < 0)
                PARA_ERROR_LOG("%s\n", para_strerror(-ret));
        return ret < 0? EXIT_FAILURE : EXIT_SUCCESS;
index 8fbd0a0..778318c 100644 (file)
--- a/audiod.c
+++ b/audiod.c
@@ -1,23 +1,27 @@
 /*
- * Copyright (C) 2005-2009 Andre Noll <maan@systemlinux.org>
+ * Copyright (C) 2005-2010 Andre Noll <maan@systemlinux.org>
  *
  * Licensed under the GPL v2. For licencing details see COPYING.
  */
 
 /** \file audiod.c the paraslash's audio daemon */
+#include <regex.h>
 #include <sys/types.h>
 #include <dirent.h>
 #include <signal.h>
+#include <openssl/rc4.h>
+#include <stdbool.h>
 
 #include "para.h"
 #include "error.h"
+#include "crypt.h"
 #include "audiod.cmdline.h"
 #include "list.h"
 #include "sched.h"
 #include "ggo.h"
 #include "recv.h"
+#include "buffer_tree.h"
 #include "filter.h"
-#include "grab_client.cmdline.h"
 #include "grab_client.h"
 #include "client.cmdline.h"
 #include "client.h"
@@ -88,6 +92,7 @@ struct status_task {
        struct timeval restart_barrier;
        /** Last time we received status data from para_server. */
        struct timeval last_status_read;
+       size_t min_iqs;
        /** The offset value announced by para_server. */
        int offset_seconds;
        /** The length of the current audio file as announced by para_server. */
@@ -106,6 +111,8 @@ struct status_task {
        struct timeval clock_diff_barrier;
        /** Number of the audio format as announced by para_server. */
        int current_audio_format_num;
+       /* The status task btrn is the child of the client task. */
+       struct btr_node *btrn;
 };
 
 /** The array of status items sent by para_server. */
@@ -136,7 +143,6 @@ static struct status_task status_task_struct;
  * \sa struct status_task
  */
 static struct status_task *stat_task = &status_task_struct;
-static struct timeval initial_delay_barrier;
 
 /**
  * the task for handling audiod commands
@@ -154,13 +160,14 @@ struct command_task {
 #define FOR_EACH_AUDIO_FORMAT(af) for (af = 0; af < NUM_AUDIO_FORMATS; af++)
 
 /**
- * get the audio format number
- * \param name the name of the audio format
+ * Get the audio format number.
+ *
+ * \param name The name of the audio format.
  *
  * \return The audio format number on success, -E_UNSUPPORTED_AUDIO_FORMAT if
  * \a name is not a supported audio format.
  */
-int get_audio_format_num(char *name)
+int get_audio_format_num(const char *name)
 {
        int i;
 
@@ -172,13 +179,37 @@ int get_audio_format_num(char *name)
        return -E_UNSUPPORTED_AUDIO_FORMAT;
 }
 
+/**
+ * Compute the play time based on information of the given slot.
+ *
+ * \param slot_num The slot number (negative means: no slot).
+ *
+ * This computes a string of the form "0:07 [3:33] (3%/3:40)" using information
+ * from the status items received from para_server and the start time of the
+ * (first) writer of the given slot.
+ *
+ * It has to to take into account that probably the stream was not started at
+ * the beginning of the file, that the clock between the server and the client
+ * host may differ and that playback of the stream was delayed, e.g. because
+ * the prebuffer filter is used in the filter configuration of the given slot.
+ *
+ * If no writer is active in the given slot, or \a slot_num is negative
+ * (indicating that para_audiod runs in standby mode), an approximation based
+ * only on the status items is computed and the returned string is prefixed
+ * with "~".
+ *
+ * \return A string that must be freed by the caller.
+ */
 char *get_time_string(int slot_num)
 {
        int ret, seconds = 0, length;
        struct timeval *tmp, sum, sss, /* server stream start */
+               rstime, /* receiver start time */
+               wstime, /* writer start time */
                wtime, /* now - writer start */
                rskip; /* receiver start - sss */
        struct slot_info *s = slot_num < 0? NULL : &slot[slot_num];
+       char *msg;
 
        if (audiod_status == AUDIOD_OFF)
                goto empty;
@@ -189,27 +220,41 @@ char *get_time_string(int slot_num)
        }
        if (audiod_status == AUDIOD_ON && !s)
                goto empty;
-       /* valid status items and playing */
-       if (s) { /* writer active in this slot */
-               length = s->seconds_total;
-               tmp = &s->server_stream_start;
-       } else { /* standby mode, rely on status items */
-               length = stat_task->length_seconds;
-               tmp = &stat_task->server_stream_start;
+       /*
+        * Valid status items and playing, set length and tmp to the stream
+        * start. We use the slot info and fall back to the info from current
+        * status items if no slot info is available.
+        */
+       length = stat_task->length_seconds;
+       tmp = &stat_task->server_stream_start;
+       if (s && s->wns) { /* writer active in this slot */
+               btr_get_node_start(s->wns[0].btrn, &wstime);
+               if (wstime.tv_sec != 0) { /* writer wrote something */
+                       if (s->server_stream_start.tv_sec == 0) {
+                               /* copy status info to slot */
+                               s->server_stream_start = stat_task->server_stream_start;
+                               s->offset_seconds = stat_task->offset_seconds;
+                               s->seconds_total = stat_task->length_seconds;
+                       }
+                       length = s->seconds_total;
+                       tmp = &s->server_stream_start;
+               }
        }
        if (stat_task->sa_time_diff_sign > 0)
                tv_diff(tmp, &stat_task->sa_time_diff, &sss);
        else
                tv_add(tmp, &stat_task->sa_time_diff, &sss);
-       if (!s) {
+       if (!s || !s->wns) {
                struct timeval diff;
                tv_diff(now, &sss, &diff);
                seconds = diff.tv_sec + stat_task->offset_seconds;
                goto out;
        }
-       tv_diff(now, &s->wstime, &wtime);
+       tv_diff(now, &wstime, &wtime);
+       //PARA_CRIT_LOG("offset %d\n", s->offset_seconds);
        seconds = s->offset_seconds;
-       ret = tv_diff(&s->rstime, &sss, &rskip);
+       btr_get_node_start(s->receiver_node->btrn, &rstime);
+       ret = tv_diff(&rstime, &sss, &rskip);
        if (ret > 0) { /* audiod was started in the middle of the stream */
                tv_add(&wtime, &rskip, &sum);
                seconds += sum.tv_sec;
@@ -218,9 +263,8 @@ char *get_time_string(int slot_num)
 out:
        seconds = PARA_MIN(seconds, length);
        seconds = PARA_MAX(seconds, 0);
-       return make_message(
-               "%s: %s%d:%02d [%d:%02d] (%d%%/%d:%02d)\n",
-               status_item_list[SI_PLAY_TIME],
+       msg = make_message(
+               "%s%d:%02d [%d:%02d] (%d%%/%d:%02d)",
                s? "" : "~",
                seconds / 60,
                seconds % 60,
@@ -230,8 +274,10 @@ out:
                length / 60,
                length % 60
        );
+       PARA_DEBUG_LOG("slot %d: %s\n", slot_num, msg);
+       return msg;
 empty:
-       return make_message("%s:\n", status_item_list[SI_PLAY_TIME]);
+       return para_strdup(NULL);
 }
 
 static int want_colors(void)
@@ -309,30 +355,96 @@ static void close_receiver(int slot_num)
        PARA_NOTICE_LOG("closing %s receiver in slot %d\n",
                audio_formats[s->format], slot_num);
        a->receiver->close(s->receiver_node);
+       btr_free_node(s->receiver_node->btrn);
        free(s->receiver_node);
        s->receiver_node = NULL;
-       stat_task->current_audio_format_num = -1;
 }
 
-static void kill_all_decoders(int error)
+static void writer_cleanup(struct writer_node *wn)
+{
+       struct writer *w;
+
+       if (!wn)
+               return;
+       w = writers + wn->writer_num;
+       PARA_INFO_LOG("closing %s\n", writer_names[wn->writer_num]);
+       w->close(wn);
+       btr_free_node(wn->btrn);
+}
+
+static void close_writers(struct slot_info *s)
 {
+       struct audio_format_info *a;
        int i;
 
+       if (s->format < 0)
+               return;
+       assert(s->wns);
+       a = afi + s->format;
+       if (a->num_writers == 0)
+               writer_cleanup(s->wns);
+       else {
+               for (i = 0; i < a->num_writers; i++)
+                       writer_cleanup(s->wns + i);
+       }
+       free(s->wns);
+       s->wns = NULL;
+}
+
+static void close_filters(struct slot_info *s)
+{
+       int i;
+       struct audio_format_info *a = afi + s->format;
+       if (a->num_filters == 0)
+               return;
+       for (i = 0; i < a->num_filters; i++) {
+               struct filter_node *fn = s->fns + i;
+               struct filter *f;
+
+               if (!fn)
+                       continue;
+               f = filters + fn->filter_num;
+               if (f->close)
+                       f->close(fn);
+               btr_free_node(fn->btrn);
+       }
+       free(s->fns);
+       s->fns = NULL;
+}
+
+/*
+ * Whenever a task commits suicide by returning from post_select with t->error
+ * < 0, it also removes its btr node. We do exactly that to kill a running
+ * task. Note that the scheduler checks t->error also _before_ each pre/post
+ * select call, so the victim will never be scheduled again.
+ */
+static void kill_btrn(struct btr_node *btrn, struct task *t, int error)
+{
+       if (t->error < 0)
+               return;
+       t->error = error;
+       btr_remove_node(btrn);
+}
+
+static void kill_all_decoders(int error)
+{
+       int i, j;
+
        FOR_EACH_SLOT(i) {
                struct slot_info *s = &slot[i];
-               if (s->wng && s->wng->task.error >= 0) {
-                       PARA_INFO_LOG("deactivating wng in slot %d\n",
-                               i);
-                       s->wng->task.error = error;
-               }
-               if (s->fc && s->fc->task.error >= 0) {
-                       PARA_INFO_LOG("deactivatimg filter chain in slot %d\n", i);
-                       s->fc->task.error = error;
-               }
-               if (s->receiver_node && s->receiver_node->task.error >= 0) {
-                       PARA_INFO_LOG("deactivating receiver_node in slot %d\n", i);
-                       s->receiver_node->task.error = error;
-               }
+               struct audio_format_info *a;
+               if (s->format < 0)
+                       continue;
+               a = afi + s->format;
+               if (s->wns)
+                       for (j = 0; j < a->num_writers; j++)
+                               kill_btrn(s->wns[j].btrn, &s->wns[j].task, error);
+               if (s->fns)
+                       for (j = 0; j < a->num_writers; j++)
+                               kill_btrn(s->fns[j].btrn, &s->wns[j].task, error);
+               if (s->receiver_node)
+                       kill_btrn(s->receiver_node->btrn, &s->receiver_node->task,
+                               error);
        }
 }
 
@@ -347,7 +459,7 @@ static int get_empty_slot(void)
                        clear_slot(i);
                        return i;
                }
-               if (s->wng || s->receiver_node || s->fc)
+               if (s->wns || s->receiver_node || s->fns)
                        continue;
                clear_slot(i);
                return i;
@@ -369,172 +481,173 @@ int num_filters(int audio_format_num)
        return afi[audio_format_num].num_filters;
 }
 
-static void open_filters(int slot_num)
+static void open_filters(struct slot_info *s)
 {
-       struct slot_info *s = &slot[slot_num];
-       struct audio_format_info *a = &afi[s->format];
+       struct audio_format_info *a = afi + s->format;
        struct filter_node *fn;
        int nf = a->num_filters;
+       struct btr_node *parent;
        int i;
 
-       s->fc = NULL;
-       if (!nf)
+       if (nf == 0)
                return;
        PARA_INFO_LOG("opening %s filters\n", audio_formats[s->format]);
-       s->fc = para_calloc(sizeof(struct filter_chain));
-       s->fc->filter_nodes = para_malloc(nf * sizeof(struct filter_node));
-       s->fc->inbufp = &s->receiver_node->buf;
-       s->fc->in_loaded = &s->receiver_node->loaded;
-       s->fc->input_error = &s->receiver_node->task.error;
-       s->fc->task.pre_select = NULL;
-       s->fc->task.post_select = filter_post_select;
-       s->fc->task.error = 0;
-       s->fc->num_filters = nf;
-
-       s->receiver_node->output_error = &s->fc->task.error;
-       sprintf(s->fc->task.status, "filter chain");
-       FOR_EACH_FILTER_NODE(fn, s->fc, i) {
+       assert(s->fns == NULL);
+       s->fns = para_calloc(nf * sizeof(struct filter_node));
+       parent = s->receiver_node->btrn;
+       for (i = 0; i < nf; i++) {
                struct filter *f = filters + a->filter_nums[i];
+               fn = s->fns + i;
                fn->filter_num = a->filter_nums[i];
                fn->conf = a->filter_conf[i];
-               fn->fc = s->fc;
-               fn->loaded = 0;
-               INIT_LIST_HEAD(&fn->callbacks);
+               fn->task.pre_select = f->pre_select;
+               fn->task.post_select = f->post_select;
+
+               fn->btrn = btr_new_node(&(struct btr_node_description)
+                       EMBRACE(.name = f->name, .parent = parent,
+                               .handler = f->execute, .context = fn));
+
                f->open(fn);
+               register_task(&fn->task);
+               parent = fn->btrn;
                PARA_NOTICE_LOG("%s filter %d/%d (%s) started in slot %d\n",
-                       audio_formats[s->format], i,  nf, f->name, slot_num);
-               s->fc->outbufp = &fn->buf;
-               s->fc->out_loaded = &fn->loaded;
+                       audio_formats[s->format], i,  nf, f->name, (int)(s - slot));
+               sprintf(fn->task.status, "%s (slot %d)", f->name, (int)(s - slot));
        }
-       register_task(&s->fc->task);
 }
 
-static void open_writers(int slot_num)
+static void open_writers(struct slot_info *s)
 {
-       int ret, i;
-       struct slot_info *s = &slot[slot_num];
-       struct audio_format_info *a = &afi[s->format];
-
-       PARA_INFO_LOG("opening %s writers\n", audio_formats[s->format]);
-       if (!a->num_writers)
-               s->wng = setup_default_wng();
-       else
-               s->wng = wng_new(a->num_writers);
-       if (s->fc) {
-               s->wng->bufp = s->fc->outbufp;
-               s->wng->loaded = s->fc->out_loaded;
-               s->wng->input_error = &s->fc->task.error;
-               s->wng->channels = &s->fc->channels;
-               s->wng->samplerate = &s->fc->samplerate;
-               s->fc->output_error = &s->wng->task.error;
-               PARA_INFO_LOG("samplerate: %d\n", *s->wng->samplerate);
-       } else {
-               s->wng->bufp = &s->receiver_node->buf;
-               s->wng->loaded = &s->receiver_node->loaded;
-               s->wng->input_error = &s->receiver_node->task.error;
-       }
-       for (i = 0; i < a->num_writers; i++) {
-               s->wng->writer_nodes[i].conf = a->writer_conf[i];
-               s->wng->writer_nodes[i].writer_num = a->writer_nums[i];
+       int i;
+       struct audio_format_info *a = afi + s->format;
+       struct writer_node *wn;
+       struct btr_node *parent = s->fns[a->num_filters - 1].btrn;
+
+       assert(s->wns == NULL);
+       s->wns = para_calloc(PARA_MAX(1U, a->num_writers)
+               * sizeof(struct writer_node));
+       if (a->num_writers == 0)
+               setup_writer_node(NULL, parent, s->wns);
+       else {
+               PARA_INFO_LOG("opening %s writers\n", audio_formats[s->format]);
+               for (i = 0; i < a->num_writers; i++) {
+                       wn = s->wns + i;
+                       wn->conf = a->writer_conf[i];
+                       wn->writer_num = a->writer_nums[i];
+                       register_writer_node(wn, parent);
+               }
        }
-       ret = wng_open(s->wng);
-       if (ret < 0)
-               return;
-       s->wstime = *now;
-       s->server_stream_start = stat_task->server_stream_start.tv_sec?
-               stat_task->server_stream_start : *now;
-       s->offset_seconds = stat_task->offset_seconds;
-       s->seconds_total = stat_task->length_seconds;
-       activate_inactive_grab_clients(slot_num, s->format, s->fc);
 }
 
+/* returns slot num on success */
 static int open_receiver(int format)
 {
        struct audio_format_info *a = &afi[format];
        struct slot_info *s;
        int ret, slot_num;
+       struct receiver *r = a->receiver;
        struct receiver_node *rn;
-       const struct timeval restart_delay = {2, 0};
 
+       tv_add(now, &(struct timeval)EMBRACE(2, 0), &a->restart_barrier);
        ret = get_empty_slot();
        if (ret < 0)
-               goto err;
+               return ret;
        slot_num = ret;
-       s = &slot[slot_num];
-       s->format = format;
-       s->receiver_node = para_calloc(sizeof(struct receiver_node));
-       rn = s->receiver_node;
-       rn->receiver = a->receiver;
+       rn = para_calloc(sizeof(*rn));
+       rn->receiver = r;
        rn->conf = a->receiver_conf;
-       ret = a->receiver->open(s->receiver_node);
+       rn->btrn = btr_new_node(&(struct btr_node_description)
+               EMBRACE(.name = r->name, .context = rn));
+       ret = r->open(rn);
        if (ret < 0) {
-               free(s->receiver_node);
-               s->receiver_node = NULL;
-               goto err;
+               btr_free_node(rn->btrn);
+               free(rn);
+               return ret;
        }
+       s = &slot[slot_num];
+       s->format = format;
+       s->receiver_node = rn;
        PARA_NOTICE_LOG("started %s: %s receiver in slot %d\n",
-               audio_formats[s->format], a->receiver->name, slot_num);
-       rn->task.pre_select = a->receiver->pre_select;
-       rn->task.post_select = a->receiver->post_select;
-       s->rstime = *now;
-       sprintf(rn->task.status, "%s receiver node", rn->receiver->name);
+               audio_formats[format], r->name, slot_num);
+       rn->task.pre_select = r->pre_select;
+       rn->task.post_select = r->post_select;
+       sprintf(rn->task.status, "%s receiver node", r->name);
        register_task(&rn->task);
-       ret = 1;
-err:
-       if (ret < 0)
-               PARA_ERROR_LOG("%s\n", para_strerror(-ret));
-       tv_add(now, &restart_delay, &afi[format].restart_barrier);
-       return ret;
+       return slot_num;
 }
 
-/* return: 0: Not running, 1: Running, -1: Running but eof (or error) */
-static int receiver_running(int format)
+static bool receiver_running(void)
 {
-       int i, ret = 0;
+       int i;
+       long unsigned ss1 = stat_task->server_stream_start.tv_sec;
 
        FOR_EACH_SLOT(i) {
                struct slot_info *s = &slot[i];
-               if (s->format != format)
-                       continue;
+               long unsigned ss2 = s->server_stream_start.tv_sec;
+
                if (!s->receiver_node)
                        continue;
                if (s->receiver_node->task.error >= 0)
-                       return 1;
-               ret = -1;
+                       return true;
+               if (ss1 == ss2)
+                       return true;
        }
-       return ret;
+       return false;
 }
 
-static void open_current_receiver(struct sched *s)
+/**
+ * Return the root node of the current buffer tree.
+ *
+ * This is only used for stream grabbing.
+ *
+ * \return \p NULL if no slot is currently active. If more than one buffer tree
+ * exists, the node corresponding to the most recently started receiver is
+ * returned.
+ */
+struct btr_node *audiod_get_btr_root(void)
 {
-       struct timeval diff;
-       int ret, cafn = stat_task->current_audio_format_num;
+       int i, newest_slot = -1;
+       struct timeval newest_rstime = {0, 0};
 
-       if (cafn < 0 || !stat_task->ct)
-               return;
-       /* Do nothing if the 'N' flag is set or the 'P' flag is unset */
-       if (stat_task->vss_status != VSS_STATUS_FLAG_PLAYING)
-               return;
-       ret = receiver_running(cafn);
-       if (ret > 0) /* already running and not eof */
-               return;
-       if (ret < 0) { /* eof */
-               /*
-                * para_server uses a zero start time during the announcement
-                * period, i.e. before it sends the first chunk. Wait until
-                * this period begins to avoid restarting the receiver that
-                * belongs to the file just completed.
-                */
-               if (stat_task->server_stream_start.tv_sec)
-                       return;
-       }
-       if (tv_diff(now, &afi[cafn].restart_barrier, &diff) < 0) {
-               /* avoid busy loop */
-               s->timeout = diff;
-               return;
+       FOR_EACH_SLOT(i) {
+               struct slot_info *s = &slot[i];
+               struct timeval rstime;
+               if (!s->receiver_node)
+                       continue;
+               if (s->receiver_node->task.error < 0)
+                       continue;
+               btr_get_node_start(s->receiver_node->btrn, &rstime);
+               if (newest_slot >= 0 && tv_diff(&rstime, &newest_rstime, NULL) < 0)
+                       continue;
+               newest_rstime = rstime;
+               newest_slot = i;
        }
-       /* start a new receiver */
-       open_receiver(cafn);
+       if (newest_slot == -1)
+               return NULL;
+       return slot[newest_slot].receiver_node->btrn;
+}
+
+/* whether a new instance of a decoder should be started. */
+static bool must_start_decoder(void)
+{
+       int cafn = stat_task->current_audio_format_num;
+       unsigned vs = stat_task->vss_status;
+
+       if (audiod_status != AUDIOD_ON)
+               return false;
+       if (cafn < 0)
+               return false;
+       if (!stat_task->ct)
+               return false;
+       if (vs & VSS_STATUS_FLAG_NEXT)
+               return false;
+       if (!(vs & VSS_STATUS_FLAG_PLAYING))
+               return false;
+       if (receiver_running())
+               return false;
+       if (tv_diff(now, &afi[cafn].restart_barrier, NULL) < 0)
+               return false;
+       return true;
 }
 
 static unsigned compute_time_diff(const struct timeval *status_time)
@@ -569,9 +682,9 @@ static unsigned compute_time_diff(const struct timeval *status_time)
                &tmp);
        stat_task->sa_time_diff = tmp;
        PARA_INFO_LOG("time diff (cur/avg): %s%lums/%s%lums\n",
-               sign > 0? "+" : "-",
+               sign < 0? "-" : "+",
                tv2ms(&diff),
-               sa_time_diff_sign ? "+" : "-",
+               sa_time_diff_sign < 0? "-" : "+",
                tv2ms(&stat_task->sa_time_diff)
        );
 out:
@@ -579,70 +692,44 @@ out:
        return count;
 }
 
-static int check_stat_line(char *line, __a_unused void *data)
+static int update_item(int itemnum, char *buf)
 {
-       int itemnum;
-       size_t ilen = 0;
        long unsigned sec, usec;
-       char *tmp;
 
-       //PARA_INFO_LOG("line: %s\n", line);
-       if (!line)
-               return 1;
-       itemnum = stat_line_valid(line);
-       if (itemnum < 0) {
-               PARA_WARNING_LOG("invalid status line: %s\n", line);
-               return 1;
-       }
        if (stat_task->clock_diff_count && itemnum != SI_CURRENT_TIME)
                return 1;
-       tmp = make_message("%s\n", line);
-       stat_client_write(tmp, itemnum);
-       free(tmp);
        free(stat_item_values[itemnum]);
-       stat_item_values[itemnum] = para_strdup(line);
-       ilen = strlen(status_item_list[itemnum]);
+       stat_item_values[itemnum] = para_strdup(buf);
+       stat_client_write_item(itemnum);
        switch (itemnum) {
        case SI_STATUS_FLAGS:
                stat_task->vss_status = 0;
-               if (strchr(line, 'N'))
+               if (strchr(buf, 'N'))
                        stat_task->vss_status |= VSS_STATUS_FLAG_NEXT;
-               if (strchr(line, 'P'))
+               if (strchr(buf, 'P'))
                        stat_task->vss_status |= VSS_STATUS_FLAG_PLAYING;
                break;
        case SI_OFFSET:
-               stat_task->offset_seconds = atoi(line + ilen + 1);
+               stat_task->offset_seconds = atoi(buf);
                break;
        case SI_SECONDS_TOTAL:
-               stat_task->length_seconds = atoi(line + ilen + 1);
+               stat_task->length_seconds = atoi(buf);
                break;
        case SI_STREAM_START:
-               if (sscanf(line + ilen + 1, "%lu.%lu", &sec, &usec) == 2) {
-                       struct timeval a_start, delay;
-                       delay.tv_sec = conf.stream_delay_arg / 1000;
-                       delay.tv_usec = (conf.stream_delay_arg % 1000) * 1000;
+               if (sscanf(buf, "%lu.%lu", &sec, &usec) == 2) {
                        stat_task->server_stream_start.tv_sec = sec;
                        stat_task->server_stream_start.tv_usec = usec;
-                       if (compute_time_diff(NULL) > 2) {
-                               if (stat_task->sa_time_diff_sign < 0)
-                                       tv_add(&stat_task->server_stream_start,
-                                               &stat_task->sa_time_diff, &a_start);
-                               else
-                                       tv_diff(&stat_task->server_stream_start,
-                                               &stat_task->sa_time_diff, &a_start);
-                               tv_add(&a_start, &delay, &initial_delay_barrier);
-                       }
                }
                break;
        case SI_CURRENT_TIME:
-               if (sscanf(line + ilen + 1, "%lu.%lu", &sec, &usec) == 2) {
+               if (sscanf(buf, "%lu.%lu", &sec, &usec) == 2) {
                        struct timeval tv = {sec, usec};
                        compute_time_diff(&tv);
                }
                break;
        case SI_FORMAT:
-               stat_task->current_audio_format_num = get_audio_format_num(
-                       line + ilen + 1);
+               stat_task->current_audio_format_num
+                       = get_audio_format_num(buf);
        }
        return 1;
 }
@@ -772,6 +859,17 @@ static int init_default_filters(void)
 
                if (a->num_filters)
                        continue; /* no default -- nothing to to */
+               /*
+                * If udp is used to receive this audiod format, add fecdec as
+                * the first filter.
+                */
+               if (strcmp(afi[i].receiver->name, "udp") == 0) {
+                       tmp = para_strdup("fecdec");
+                       add_filter(i, tmp);
+                       free(tmp);
+                       if (ret < 0)
+                               goto out;
+               }
                /* add "dec" to audio format name */
                tmp = make_message("%sdec", audio_formats[i]);
                for (j = 0; filters[j].name; j++)
@@ -797,7 +895,7 @@ static int parse_filter_args(void)
 {
        int i, ret, nf;
 
-       nf = PARA_MAX(1U, conf.filter_given);
+       nf = PARA_MAX(2U, conf.filter_given);
        PARA_INFO_LOG("maximal number of filters: %d\n", nf);
        FOR_EACH_AUDIO_FORMAT(i) {
                afi[i].filter_conf = para_malloc(nf * sizeof(void *));
@@ -882,19 +980,16 @@ static void signal_pre_select(struct sched *s, struct task *t)
        para_fd_set(st->fd, &s->rfds, &s->max_fileno);
 }
 
-static void signal_post_select(struct sched *s, struct task *t)
+static void signal_post_select(struct sched *s, __a_unused struct task *t)
 {
-       struct signal_task *st = container_of(t, struct signal_task, task);
-
-       if (!FD_ISSET(st->fd, &s->rfds))
-               return;
+       int signum;
 
-       st->signum = para_next_signal();
-       switch (st->signum) {
+       signum = para_next_signal(&s->rfds);
+       switch (signum) {
        case SIGINT:
        case SIGTERM:
        case SIGHUP:
-               PARA_EMERG_LOG("terminating on signal %d\n", st->signum);
+               PARA_EMERG_LOG("terminating on signal %d\n", signum);
                clean_exit(EXIT_FAILURE, "caught deadly signal");
        }
 }
@@ -916,13 +1011,19 @@ static void command_post_select(struct sched *s, struct task *t)
 {
        int ret;
        struct command_task *ct = container_of(t, struct command_task, task);
+       static struct timeval last_status_dump;
+       struct timeval tmp, delay = {0, 500 * 1000};
 
-       audiod_status_dump();
-       if (!FD_ISSET(ct->fd, &s->rfds))
-               return;
-       ret = handle_connect(ct->fd);
+       tv_add(&last_status_dump, &delay, &tmp);
+       if (tv_diff(&tmp, now, NULL) < 0) {
+               audiod_status_dump();
+               last_status_dump = *now;
+       }
+
+       ret = handle_connect(ct->fd, &s->rfds);
        if (ret < 0)
                PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+       audiod_status_dump();
 }
 
 static void init_command_task(struct command_task *ct)
@@ -936,17 +1037,12 @@ static void init_command_task(struct command_task *ct)
 
 static void close_stat_pipe(void)
 {
-       int i;
-
        if (!stat_task->ct)
                return;
+       btr_free_node(stat_task->ct->btrn);
        client_close(stat_task->ct);
        stat_task->ct = NULL;
-       FOR_EACH_STATUS_ITEM(i) {
-               free(stat_item_values[i]);
-               stat_item_values[i] = NULL;
-       }
-       dump_empty_status();
+       clear_and_dump_items();
        stat_task->length_seconds = 0;
        stat_task->offset_seconds = 0;
        stat_task->vss_status = 0;
@@ -985,19 +1081,27 @@ static void set_stat_task_restart_barrier(unsigned seconds)
 static void try_to_close_slot(int slot_num)
 {
        struct slot_info *s = &slot[slot_num];
+       struct audio_format_info *a = afi + s->format;
+       int i;
 
        if (s->format < 0)
                return;
        if (s->receiver_node && s->receiver_node->task.error != -E_TASK_UNREGISTERED)
                return;
-       if (s->fc && s->fc->task.error != -E_TASK_UNREGISTERED)
-               return;
-       if (s->wng && s->wng->task.error != -E_TASK_UNREGISTERED)
-               return;
+       for (i = 0; i < a->num_filters; i++)
+               if (s->fns && s->fns[i].task.error != -E_TASK_UNREGISTERED)
+                       return;
+       if (a->num_writers > 0) {
+               for (i = 0; i < a->num_writers; i++)
+                       if (s->wns && s->wns[i].task.error != -E_TASK_UNREGISTERED)
+                               return;
+       } else {
+               if (s->wns && s->wns[0].task.error != -E_TASK_UNREGISTERED)
+                       return;
+       }
        PARA_INFO_LOG("closing slot %d\n", slot_num);
-       wng_close(s->wng);
-       close_filters(s->fc);
-       free(s->fc);
+       close_writers(s);
+       close_filters(s);
        close_receiver(slot_num);
        clear_slot(slot_num);
 }
@@ -1006,54 +1110,63 @@ static void try_to_close_slot(int slot_num)
  * Check if any receivers/filters/writers need to be started and do so if
  * necessary.
  */
-static void start_stop_decoders(struct sched *s)
+static void start_stop_decoders(void)
 {
-       int i;
+       int i, ret;
+       struct slot_info *sl;
+       struct audio_format_info *a;
 
        FOR_EACH_SLOT(i)
                try_to_close_slot(i);
        if (audiod_status != AUDIOD_ON ||
                        !(stat_task->vss_status & VSS_STATUS_FLAG_PLAYING))
                return kill_all_decoders(-E_NOT_PLAYING);
-       open_current_receiver(s);
-       FOR_EACH_SLOT(i) {
-               struct slot_info *sl = &slot[i];
-               struct audio_format_info *a;
-               struct timeval diff;
-
-               if (sl->format < 0)
-                       continue;
-               a = &afi[sl->format];
-               if (!sl->receiver_node)
-                       continue;
-               if ((!a->num_filters || sl->fc) && sl->wng)
-                       continue; /* everything already started */
-               if (!a->num_filters) {
-                       if (sl->receiver_node->loaded && !sl->wng) {
-                               open_writers(i);
-                       }
-                       continue;
-               }
-               if (sl->receiver_node->loaded && !sl->fc) {
-                       open_filters(i);
-                       continue;
-               }
-               if (sl->wng || !sl->fc || !*sl->fc->out_loaded)
-                       continue;
-               if (tv_diff(now, &initial_delay_barrier, &diff) > 0) {
-                       open_writers(i);
-                       continue;
-               }
-               PARA_INFO_LOG("initial delay: %lu ms left\n", tv2ms(&diff));
-               if (tv_diff(&s->timeout, &diff, NULL) > 0) {
-                       s->timeout = diff;
-               }
+       if (!must_start_decoder())
+               return;
+       ret = open_receiver(stat_task->current_audio_format_num);
+       if (ret < 0) {
+               PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+               return;
        }
+       sl = slot + ret;
+       a = afi + sl->format;
+       if (a->num_filters)
+               open_filters(sl);
+       open_writers(sl);
+       activate_grab_clients();
+       btr_log_tree(sl->receiver_node->btrn, LL_NOTICE);
 }
 
+static void status_pre_select(struct sched *s, struct task *t)
+{
+       struct status_task *st = container_of(t, struct status_task, task);
+       int ret, cafn = stat_task->current_audio_format_num;
+
+       if (must_start_decoder())
+               goto min_delay;
+       ret = btr_node_status(st->btrn, 0, BTR_NT_LEAF);
+       if (ret > 0)
+               goto min_delay;
+       if (st->ct && audiod_status == AUDIOD_OFF)
+               goto min_delay;
+       if (!st->ct && audiod_status != AUDIOD_OFF)
+               sched_request_barrier_or_min_delay(&st->restart_barrier, s);
+       if (cafn >= 0)
+               sched_request_barrier(&afi[cafn].restart_barrier, s);
+       /*
+        * If para_server is playing we'd like to have a smooth time display
+        * even if we are running in standby mode. So we request a timeout that
+        * expires at the next full second.
+        */
+       if (stat_task->vss_status & VSS_STATUS_FLAG_PLAYING)
+               sched_request_timeout_ms(1000 - now->tv_usec / 1000, s);
+       return;
+min_delay:
+       sched_min_delay(s);
+}
 
 /* restart the client task if necessary */
-static void status_pre_select(struct sched *s, struct task *t)
+static void status_post_select(__a_unused struct sched *s, struct task *t)
 {
        struct status_task *st = container_of(t, struct status_task, task);
 
@@ -1061,7 +1174,7 @@ static void status_pre_select(struct sched *s, struct task *t)
                if (!st->ct)
                        goto out;
                if (st->ct->task.error >= 0) {
-                       st->ct->task.error = -E_AUDIOD_OFF;
+                       kill_btrn(st->ct->btrn, &st->ct->task, -E_AUDIOD_OFF);
                        goto out;
                }
                if (st->ct->task.error != -E_TASK_UNREGISTERED)
@@ -1071,7 +1184,9 @@ static void status_pre_select(struct sched *s, struct task *t)
                goto out;
        }
        if (st->ct) {
-               unsigned bytes_left;
+               char *buf;
+               size_t sz;
+               int ret;
                if (st->ct->task.error < 0) {
                        if (st->ct->task.error != -E_TASK_UNREGISTERED)
                                goto out;
@@ -1080,54 +1195,66 @@ static void status_pre_select(struct sched *s, struct task *t)
                }
                if (st->ct->status != CL_RECEIVING)
                        goto out;
-               bytes_left = for_each_line(st->ct->buf, st->ct->loaded,
-                       &check_stat_line, NULL);
-               if (st->ct->loaded != bytes_left) {
-                       st->last_status_read = *now;
-                       st->ct->loaded = bytes_left;
-               } else {
+               ret = btr_node_status(st->btrn, st->min_iqs, BTR_NT_LEAF);
+               if (ret <= 0) {
                        struct timeval diff;
                        tv_diff(now, &st->last_status_read, &diff);
                        if (diff.tv_sec > 61)
-                               st->ct->task.error = -E_STATUS_TIMEOUT;
+                               kill_btrn(st->ct->btrn, &st->ct->task,
+                                       -E_STATUS_TIMEOUT);
+                       goto out;
                }
+               btr_merge(st->btrn, st->min_iqs);
+               sz = btr_next_buffer(st->btrn, &buf);
+               ret = for_each_stat_item(buf, sz, update_item);
+               if (ret < 0) {
+                       kill_btrn(st->ct->btrn, &st->ct->task, ret);
+                       goto out;
+               }
+               if (sz != ret) {
+                       btr_consume(st->btrn, sz - ret);
+                       st->last_status_read = *now;
+                       st->min_iqs = 0;
+               } else /* current status item crosses buffers */
+                       st->min_iqs = sz + 1;
                goto out;
        }
        if (tv_diff(now, &st->restart_barrier, NULL) < 0)
                goto out;
        if (st->clock_diff_count) { /* get status only one time */
-               char *argv[] = {"audiod", "stat", "1", NULL};
-               int argc = 3;
+               char *argv[] = {"audiod", "--", "stat", "-p", "-n=1", NULL};
+               int argc = 5;
                PARA_INFO_LOG("clock diff count: %d\n", st->clock_diff_count);
                st->clock_diff_count--;
-               client_open(argc, argv, &st->ct, NULL);
+               client_open(argc, argv, &st->ct, NULL, NULL, st->btrn);
                set_stat_task_restart_barrier(2);
 
        } else {
-               char *argv[] = {"audiod", "stat", NULL};
-               int argc = 2;
-               client_open(argc, argv, &st->ct, NULL);
+               char *argv[] = {"audiod", "--", "stat", "-p", NULL};
+               int argc = 4;
+               client_open(argc, argv, &st->ct, NULL, NULL, st->btrn);
                set_stat_task_restart_barrier(5);
        }
        free(stat_item_values[SI_BASENAME]);
-       stat_item_values[SI_BASENAME] = make_message(
-               "%s: no connection to para_server\n",
-               status_item_list[SI_BASENAME]);
-       stat_client_write(stat_item_values[SI_BASENAME],
-               SI_BASENAME);
+       stat_item_values[SI_BASENAME] = para_strdup(
+               "no connection to para_server");
+       stat_client_write_item(SI_BASENAME);
        st->last_status_read = *now;
 out:
-       start_stop_decoders(s);
+       start_stop_decoders();
 }
 
 static void init_status_task(struct status_task *st)
 {
        memset(st, 0, sizeof(struct status_task));
        st->task.pre_select = status_pre_select;
+       st->task.post_select = status_post_select;
        st->sa_time_diff_sign = 1;
        st->clock_diff_count = conf.clock_diff_count_arg;
        st->current_audio_format_num = -1;
-       sprintf(st->task.status, "status task");
+       sprintf(st->task.status, "stat");
+       st->btrn = btr_new_node(&(struct btr_node_description)
+               EMBRACE(.name = "stat"));
 }
 
 static void set_initial_status(void)
@@ -1215,9 +1342,12 @@ int main(int argc, char *argv[])
        drop_privileges_or_die(conf.user_arg, conf.group_arg);
        parse_config_or_die();
        init_colors_or_die();
+       init_random_seed_or_die();
        daemon_set_flag(DF_LOG_TIME);
        daemon_set_flag(DF_LOG_HOSTNAME);
        daemon_set_flag(DF_LOG_LL);
+       if (conf.log_timing_given)
+               daemon_set_flag(DF_LOG_TIMING);
        if (conf.logfile_given) {
                daemon_set_logfile(conf.logfile_arg);
                daemon_open_log_or_die();
@@ -1232,7 +1362,6 @@ int main(int argc, char *argv[])
        set_initial_status();
        FOR_EACH_SLOT(i)
                clear_slot(i);
-       init_grabbing();
        setup_signal_handling();
        signal_setup_default(sig_task);
 
@@ -1245,8 +1374,8 @@ int main(int argc, char *argv[])
        register_task(&sig_task->task);
        register_task(&cmd_task->task);
        register_task(&stat_task->task);
-       s.default_timeout.tv_sec = 0;
-       s.default_timeout.tv_usec = 99 * 1000;
+       s.default_timeout.tv_sec = 2;
+       s.default_timeout.tv_usec = 999 * 1000;
        ret = schedule(&s);
 
        PARA_EMERG_LOG("%s\n", para_strerror(-ret));
index 5469476..7ffe98e 100644 (file)
@@ -14,11 +14,32 @@ H: on -> standby -> off -> on
 N: grab
 D: grab the audio stream
 L:
-U: -- grab [grab_options]
-H: grab ('splice') the audio stream at any position in the filter
-H: chain and send that data back to the client. Try
-H:     para_audioc -- grab -h
-H: for the list of available options.
+U: -- grab [-m[{s|p|a}]] [-p=<parent>] [-n=<name>] [-o]
+H:
+H: grab ('splice') the audio stream at any position in the buffer
+H: tree and send that data back to the client.
+H:
+H: Options:
+H:
+H: -m  Change grab mode. Defaults to sloppy grab if not given.
+H:
+H:             -ms: sloppy grab
+H:
+H:             -mp: pedantic grab
+H:
+H:             -ma: aggressive grab
+H:
+H:     The various grab modes only differ in what happens if the
+H:     file descriptor to write the grabbed audio data to is not
+H:     ready for writing (i.e. would block). Sloppy mode ignores
+H:     the write, pedantic mode aborts and aggressive mode tries
+H:     to write anyway.
+H:
+H: -p  Grab output of node <parent> of the buffer tree.
+H:
+H: -n  Name of the new buffer tree node. Defaults to 'grab'.
+H:
+H: -o  One-shot mode: Stop grabbing if audio file changes.
 ---
 N: help
 D: display command list or help for given command
@@ -51,8 +72,9 @@ H: Stop all decoders but leave connection to para_server open.
 ---
 N: stat
 D: print status information
-U: stat [item1 ...]
-H: Dump given status items (all if none given) to stdout.
+U: stat [-p] [item1 ...]
+H: Dump given status items (all if none given) to stdout. If -p is given, use
+H: parser-friendly mode.
 ---
 N: tasks
 D: list current tasks
index 8b416ce..44b430c 100644 (file)
--- a/audiod.h
+++ b/audiod.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2006-2009 Andre Noll <maan@systemlinux.org>
+ * Copyright (C) 2006-2010 Andre Noll <maan@systemlinux.org>
  *
  * Licensed under the GPL v2. For licencing details see COPYING.
  */
@@ -8,7 +8,7 @@
 
 
 int num_filters(int audio_format_num);
-int get_audio_format_num(char *name);
+int get_audio_format_num(const char *name);
 
 /** enum of audio formats supported by para_audiod */
 enum {AUDIOD_AUDIO_FORMATS_ENUM};
@@ -34,12 +34,6 @@ struct audiod_command {
        const char *name;
        /** pointer to the function that handles the command */
        int (*handler)(int, int, char**);
-       /**
-        * if the command prefers to handle the full line (rather than the usual
-        * argv[] array), it stores a pointer to the corresponding line handling
-        * function here. In this case, the above \a handler pointer must be NULL.
-        */
-       int (*line_handler)(int, char*);
        /** one-line description of the command */
        const char *description;
        /** summary of the command line options */
@@ -51,16 +45,12 @@ struct audiod_command {
 /**
  * Describes one instance of a receiver-filter-writer chain.
  *
- * \sa receiver_node, receiver, filter, filter_node, filter_chain, writer,
- * writer_node, writer_node_group.
+ * \sa receiver_node, receiver, filter, filter_node, writer, writer_node,
+ * writer_node_group.
  */
 struct slot_info {
        /** Number of the audio format in this slot. */
        int format;
-       /** Receiver start time. */
-       struct timeval rstime;
-       /** Writer start time. */
-       struct timeval wstime;
        /** The stream_start status item announced by para_server.  */
        struct timeval server_stream_start;
        /** The offset status item announced by para_server. */
@@ -69,10 +59,10 @@ struct slot_info {
        unsigned seconds_total;
        /** The receiver info associated with this slot. */
        struct receiver_node *receiver_node;
-       /** The active filter chain. */
-       struct filter_chain *fc;
-       /** The active writer node group. */
-       struct writer_node_group *wng;
+       /** The array of filter nodes. */
+       struct filter_node *fns;
+       /** The array of writers attached to the last filter. */
+       struct writer_node *wns;
 };
 
 extern struct slot_info slot[MAX_STREAM_SLOTS];
@@ -80,10 +70,13 @@ extern struct audiod_args_info conf;
 extern int audiod_status;
 
 void __noreturn clean_exit(int status, const char *msg);
-int handle_connect(int accept_fd);
+int handle_connect(int accept_fd, fd_set *rfds);
 void audiod_status_dump(void);
-void dump_empty_status(void);
 char *get_time_string(int slot_num);
+struct btr_node *audiod_get_btr_root(void);
+
+void stat_client_write_item(int item_num);
+void clear_and_dump_items(void);
 
 /** iterate over all slots */
 #define FOR_EACH_SLOT(_slot) for (_slot = 0; _slot < MAX_STREAM_SLOTS; _slot++)
index 4706131..ce1aff6 100644 (file)
 /*
- * Copyright (C) 2005-2009 Andre Noll <maan@systemlinux.org>
+ * Copyright (C) 2005-2010 Andre Noll <maan@systemlinux.org>
  *
  * Licensed under the GPL v2. For licencing details see COPYING.
  */
 
 /** \file audiod_command.c commands for para_audiod */
 
+#include <regex.h>
 #include <sys/types.h>
 #include <dirent.h>
+#include <stdbool.h>
 
 #include "para.h"
 #include "audiod.cmdline.h"
 #include "list.h"
-#include "close_on_fork.h"
 #include "sched.h"
 #include "ggo.h"
+#include "buffer_tree.h"
 #include "filter.h"
-#include "grab_client.cmdline.h"
 #include "grab_client.h"
-
 #include "error.h"
 #include "audiod.h"
 #include "net.h"
 #include "daemon.h"
 #include "string.h"
+#include "write.h"
 #include "fd.h"
 #include "audiod_command_list.h"
 
 extern char *stat_item_values[NUM_STAT_ITEMS];
 
-
-/** iterate over the array of all audiod commands */
+/** Iterate over the array of all audiod commands. */
 #define FOR_EACH_COMMAND(c) for (c = 0; audiod_cmds[c].name; c++)
 
+/** The maximal number of simultaneous connections. */
+#define MAX_STAT_CLIENTS 50
+
+/** Flags used for the stat command of para_audiod. */
+enum stat_client_flags {
+       /** Enable parser-friendly output. */
+       SCF_PARSER_FRIENDLY = 1,
+};
+
+/**
+ * Describes a status client of para_audiod.
+ *
+ * There's one such structure per audiod client that sent the 'stat' command.
+ *
+ * A status client is identified by its file descriptor.  para_audiod
+ * keeps a list of connected status clients.
+ */
+struct stat_client {
+       /** The stat client's file descriptor. */
+       int fd;
+       /** Bitmask of those status items the client is interested in. */
+       uint64_t item_mask;
+       /** See \ref stat_client flags. s*/
+       unsigned flags;
+       /** Its entry in the list of stat clients. */
+       struct list_head node;
+};
+
+static INITIALIZED_LIST_HEAD(client_list);
+static int num_clients;
+
+/** The list of all status items used by para_{server,audiod,gui}. */
+const char *status_item_list[] = {STATUS_ITEM_ARRAY};
+
+static void dump_stat_client_list(void)
+{
+       struct stat_client *sc;
+
+       list_for_each_entry(sc, &client_list, node)
+               PARA_INFO_LOG("stat client on fd %d\n", sc->fd);
+}
+/**
+ * Add a status client to the list.
+ *
+ * \param fd The file descriptor of the client.
+ * \param mask Bitfield of status items for this client.
+ * \param parser_friendly Enable parser-friendly output mode.
+ *
+ * Only those status items having the bit set in \a mask will be
+ * sent to the client.
+ *
+ * \return Positive value on success, or -E_TOO_MANY_CLIENTS if
+ * the number of connected clients exceeds #MAX_STAT_CLIENTS.
+ */
+static int stat_client_add(int fd, uint64_t mask, int parser_friendly)
+{
+       struct stat_client *new_client;
+
+       if (num_clients >= MAX_STAT_CLIENTS) {
+               PARA_ERROR_LOG("maximal number of stat clients (%d) exceeded\n",
+                       MAX_STAT_CLIENTS);
+               return -E_TOO_MANY_CLIENTS;
+       }
+       PARA_INFO_LOG("adding client on fd %d\n", fd);
+       new_client = para_calloc(sizeof(struct stat_client));
+       new_client->fd = fd;
+       new_client->item_mask = mask;
+       if (parser_friendly)
+               new_client->flags = SCF_PARSER_FRIENDLY;
+       para_list_add(&new_client->node, &client_list);
+       dump_stat_client_list();
+       num_clients++;
+       return 1;
+}
+/**
+ * Write a message to all connected status clients.
+ *
+ * \param item_num The number of the status item of \a msg.
+ *
+ * On write errors, remove the status client from the client list and close its
+ * file descriptor.
+ */
+void stat_client_write_item(int item_num)
+{
+       struct stat_client *sc, *tmp;
+       struct para_buffer pb = {.flags = 0};
+       struct para_buffer pfpb = {.flags = PBF_SIZE_PREFIX};
+       const uint64_t one = 1;
+
+       list_for_each_entry_safe(sc, tmp, &client_list, node) {
+               int fd = sc->fd, ret;
+
+               if (!((one << item_num) & sc->item_mask))
+                       continue;
+               if (write_ok(fd) > 0) {
+                       struct para_buffer *b =
+                               (sc->flags & SCF_PARSER_FRIENDLY)? &pfpb : &pb;
+                       char *msg = stat_item_values[item_num];
+                       if (!b->buf)
+                               WRITE_STATUS_ITEM(b, item_num, "%s\n",
+                                       msg? msg : "");
+                       ret = write(fd, b->buf, b->offset);
+                       if (ret == b->offset)
+                               continue;
+               }
+               /* write error or fd not ready for writing */
+               close(fd);
+               num_clients--;
+               PARA_INFO_LOG("deleting client on fd %d\n", fd);
+               list_del(&sc->node);
+               free(sc);
+               dump_stat_client_list();
+       }
+       free(pb.buf);
+       free(pfpb.buf);
+//     if (num_clients)
+//             PARA_DEBUG_LOG("%d client(s)\n", num_clients);
+}
+
+/**
+ * Check if string is a known status item.
+ *
+ * \param item Buffer containing the text to check.
+ *
+ * \return If \a item is a valid status item, the number of that status item is
+ * returned. Otherwise, this function returns \p -E_UNKNOWN_STAT_ITEM.
+ */
+static int stat_item_valid(const char *item)
+{
+       int i;
+       if (!item || !*item) {
+               PARA_ERROR_LOG("%s\n", "no item");
+               return -E_UNKNOWN_STAT_ITEM;
+       }
+       FOR_EACH_STATUS_ITEM(i)
+               if (!strcmp(status_item_list[i], item))
+                       return i;
+       PARA_ERROR_LOG("invalid stat item: %s\n", item);
+       return -E_UNKNOWN_STAT_ITEM;
+}
+
 static int client_write(int fd, const char *buf)
 {
-       size_t len = strlen(buf);
+       size_t len;
+
+       if (!buf)
+               return 0;
+       len = strlen(buf);
        return write(fd, buf, len) != len? -E_CLIENT_WRITE: 1;
 }
 
@@ -43,23 +188,27 @@ __malloc static char *audiod_status_string(void)
 {
        const char *status = (audiod_status == AUDIOD_ON)?
                "on" : (audiod_status == AUDIOD_OFF)? "off": "sb";
-       return make_message("%s: %s\n", status_item_list[SI_AUDIOD_STATUS], status);
+       return para_strdup(status);
 }
 
 static int get_play_time_slot_num(void)
 {
-       int i, oldest = -1;
+       int i, oldest_slot = -1;
+       struct timeval oldest_wstime = {0, 0};
 
        FOR_EACH_SLOT(i) {
                struct slot_info *s = &slot[i];
-               if (!s->wng)
+               struct timeval wstime;
+               if (!s->wns)
                        continue;
-               if (oldest >= 0 && tv_diff(&s->wstime, &slot[oldest].wstime,
-                               NULL) > 0)
+               btr_get_node_start(s->wns[0].btrn, &wstime);
+               if (oldest_slot >= 0 && tv_diff(&wstime, &oldest_wstime, NULL) > 0)
                        continue;
-               oldest = i;
+               oldest_wstime = wstime;
+               oldest_slot = i;
        }
-       return oldest;
+       //PARA_CRIT_LOG("oldest slot: %d\n", oldest_slot);
+       return oldest_slot;
 }
 
 __malloc static char *decoder_flags(void)
@@ -72,13 +221,14 @@ __malloc static char *decoder_flags(void)
                char flag = '0';
                if (s->receiver_node)
                        flag += 1;
-               if (s->wng)
+               if (s->fns)
                        flag += 2;
+               if (s->wns)
+                       flag += 4;
                flags[i] = flag;
        }
        flags[MAX_STREAM_SLOTS] = '\0';
-       return make_message("%s: %s\n", status_item_list[SI_DECODER_FLAGS],
-               flags);
+       return para_strdup(flags);
 }
 
 static int dump_commands(int fd)
@@ -168,95 +318,50 @@ int com_kill(int fd, int argc, char **argv)
 
 int com_stat(int fd, int argc, char **argv)
 {
-       int i, ret;
-       char *buf = NULL;
+       int i, ret, parser_friendly = 0;
        uint64_t mask = 0;
        const uint64_t one = 1;
+       struct para_buffer b = {.flags = 0};
 
-       if (argc > 1) {
-               for (i = 1; i < argc; i++) {
-                       ret = stat_item_valid(argv[i]);
-                       if (ret < 0)
-                               return ret;
-                       mask |= (one << ret);
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               if (arg[0] != '-')
+                       break;
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
                }
-       } else
+               if (!strncmp(arg, "-p", 2)) {
+                       parser_friendly = 1;
+                       b.flags = PBF_SIZE_PREFIX;
+                       continue;
+               }
+       }
+       if (i >= argc)
                mask--; /* set all bits */
+       for (; i < argc; i++) {
+               ret = stat_item_valid(argv[i]);
+               if (ret < 0)
+                       return ret;
+               mask |= (one << ret);
+       }
        PARA_INFO_LOG("mask: 0x%llx\n", (long long unsigned)mask);
        FOR_EACH_STATUS_ITEM(i) {
-               char *tmp, *v;
+               char *item = stat_item_values[i];
                if (!((one << i) & mask))
                        continue;
-               v = stat_item_values[i];
-               if (!v)
-                       continue;
-               tmp = make_message("%s%s%s", buf? buf: "", v,
-                       strrchr(v, '\n')? "" : "\n");
-               free(buf);
-               buf = tmp;
+               WRITE_STATUS_ITEM(&b, i, "%s\n", item? item : "");
        }
-       ret = client_write(fd, buf);
-       if (ret > 0)
-               ret = stat_client_add(fd, mask);
-       free(buf);
+       ret = client_write(fd, b.buf);
+       if (ret >= 0)
+               ret = stat_client_add(fd, mask, parser_friendly);
+       free(b.buf);
        return ret;
 }
 
-static struct filter_node *find_filter_node(int slot_num, int format, int filternum)
-{
-       int i;
-
-       FOR_EACH_SLOT(i) {
-               struct slot_info *s = &slot[i];
-               if (s->format < 0 || !s->fc)
-                       continue;
-               if (slot_num >= 0 && slot_num != i)
-                       continue;
-               if (format >= 0 && s->format != format)
-                       continue;
-               if (num_filters(i) <= filternum)
-                       continue;
-               /* success */
-               return  s->fc->filter_nodes + filternum;
-       }
-       return NULL;
-}
-
-int com_grab(int fd, char *cmdline)
+int com_grab(int fd, i