diff --git a/Makefile.am b/Makefile.am index 3d1e814e..89b1f023 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3,7 +3,7 @@ AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 -SUBDIRS = src conf debian doc web admin win32 +SUBDIRS = src conf doc web admin win32 EXTRA_DIST = HACKING config.h.vc6 m4/acx_pthread.m4 m4/ogg.m4 \ m4/theora.m4 m4/vorbis.m4 m4/speex.m4\ diff --git a/NEWS b/NEWS index 01c39b4a..45f2bce9 100644 --- a/NEWS +++ b/NEWS @@ -15,6 +15,21 @@ Feature differences from SVN trunk any extra tags are show in the conf/icecast.xml.dist file +2.3.2-kh2 +. more stats work. Clients are now processed as needed instead of a dedicated + thread per stats client. +. don't allow raw metadata updates if not from the same IP address as the source + client unless it is from the admin user. For some reason some source client + issue updates even though they are rejected. +. missing lock on streamlist master/slave update, could cause memory corruption. +. update to average bitrate handling. +. allow strftime expansion on dump filename, applies at the time the stream has + started +. removed debian directory, it's not maintained really so leave it to the debian + people to deal with it themselves. +. experimental theora keyframe as png patch added. not built by default +. minor memory leak plugged in fserve + 2.3.2-kh1 . remove stats thread. Stats clients still have their own thread and queue. . fix low bandwidth theora stream problem. diff --git a/admin/showlog.xsl b/admin/showlog.xsl index 66d5244a..673b092f 100644 --- a/admin/showlog.xsl +++ b/admin/showlog.xsl @@ -7,7 +7,7 @@ Icecast log files - + + + + + + + + + + + +
 
    
diff --git a/config.h.vc6 b/config.h.vc6
index b9a0aaac..6f2a0821 100644
--- a/config.h.vc6
+++ b/config.h.vc6
@@ -13,7 +13,7 @@
 /* Define to 1 if you have the  header file. */
 #define HAVE_CURL_CURL_H 1
 
-/* Define t o1 if you have the 'curl_global_init' function */
+/* Define to 1 if you have the 'curl_global_init' function */
 #define HAVE_CURL_GLOBAL_INIT 1
 
 /* Define to 1 if you have the `inet_aton' function. */
@@ -95,7 +95,7 @@
 #define PACKAGE_NAME "Icecast"
 
 /* Version number of package */
-#define VERSION "2.3.2-kh1"
+#define VERSION "2.3.2-kh2"
 
 /* Define to the version of this package. */
 #define PACKAGE_VERSION VERSION
@@ -162,3 +162,4 @@ typedef unsigned int socklen_t;
 /* time format for strftime */
 #define ICECAST_TIME_FMT "%a, %d %b %Y %H:%M:%S"
 
+#define PATH_MAX MAX_PATH
diff --git a/configure.in b/configure.in
index 49d341eb..48753e99 100644
--- a/configure.in
+++ b/configure.in
@@ -1,4 +1,4 @@
-AC_INIT([Icecast], [2.3.2-kh1], [karl@xiph.org])
+AC_INIT([Icecast], [2.3.2-kh2], [karl@xiph.org])
 
 AC_PREREQ(2.59)
 AC_CONFIG_SRCDIR(src/main.c)
@@ -155,7 +155,7 @@ AC_SUBST(ICECAST_OPTIONAL)
 AC_SUBST(HAVE_KATE)
 AC_SUBST(KATE_LIBS)
 
-AC_OUTPUT([Makefile conf/Makefile debian/Makefile src/Makefile src/avl/Makefile
+AC_OUTPUT([Makefile conf/Makefile src/Makefile src/avl/Makefile
 src/httpp/Makefile src/thread/Makefile src/log/Makefile
 src/net/Makefile src/timing/Makefile doc/Makefile web/Makefile web/images/Makefile
 admin/Makefile win32/Makefile win32/res/Makefile])
diff --git a/debian/Makefile.am b/debian/Makefile.am
deleted file mode 100644
index da81405e..00000000
--- a/debian/Makefile.am
+++ /dev/null
@@ -1,8 +0,0 @@
-## Process this file with automake to produce Makefile.in
-
-AUTOMAKE_OPTIONS = 1.6 foreign
-
-EXTRA_DIST = README.Debian changelog compat control copyright \
-	icecast2.1 icecast2.default icecast2.init icecast2.manpages \
-	icecast2.postinst icecast2.postrm icecast2.preinst rules watch
-
diff --git a/debian/README.Debian b/debian/README.Debian
deleted file mode 100644
index a58a065e..00000000
--- a/debian/README.Debian
+++ /dev/null
@@ -1,48 +0,0 @@
-icecast2 for Debian
--------------------
-
-In relation to the comment below by Jonas Smedegaard and chroot issues,
-I've modified the debian package so that it will install xsl pages as
-well as icecast.xml under /usr/share/icecast2, and symlink from there
-to /etc/icecast2.
-I've no idea why it would be necessary to symlink admin/ and web/ into
-/etc/icecast2, but the config file icecast.xml should be there at least
-as it's a standard location for config files.
-However the *real* file now lives in /usr/share/icecast2/etc/icecast2/icecast.xml
-because it must be available to the server when it runs in a jail (chroot)
-otherwise config reload won't work as /etc/icecast2 would normally be outside
-the jail.
-
-The only issue pending of solution is the resolver within the jail,
-which will be required mostly for the YP servers name resolution.
-I'm researching this right now.
-
- -- Rama   Sun, 2 Jul 2006 10:24:43 +0200
-
-In the Debian packaging the configuration files have been symlinked from
-the upstream location below /usr/share to /etc. This is needed to
-satisfy FHS (/usr/share are for static content only).
-If running icecast2 in a chroot environment, beware that the symlinks to
-/etc will break. A possible (untestet!) solution might be to manually
-put the configuration files back below /usr/share - and revert the hack
-again before updating the package!
-
- -- Jonas Smedegaard   Thu, 20 May 2004 21:04:27 +0200
-
-
-It is recommended to run icecast under a dedicated user account, which only
-has access to write the log files.  The Debian package creates such an
-account, named 'icecast2', and uses it by default, but you are free to
-reconfigure it and remove the account.
-
-Edit /etc/default/icecast2 to change the init-script configuration.
-
-It is possible (but discouraged for security reasons) to bind to a
-priviledged port (like standard web port 80). Edit /etc/init.d/icecast2
-to not change userid and instead set the correct userid and group in
-/etc/icecast2/icecast2.xml. Beware that this way you rely on the
-icecast2 binary to properly drop priviledges (instead of the much more
-thoroughly audited start-stop-daemon). Thanks to Jürgen A. Erhard
- for the tip.
-
- -- Keegan Quinn 
diff --git a/debian/changelog b/debian/changelog
deleted file mode 100644
index 6456ea65..00000000
--- a/debian/changelog
+++ /dev/null
@@ -1,410 +0,0 @@
-icecast2 (2.3-kh6-2) stable; urgency=low
-
-  * Non-maintainer unofficial branch upload.
-  * Fixed debian/rules and debian/icecast2.postinst so that the xsl
-    pages get installed in /usr/share/icecast2 and symlink them to
-    /etc/icecast2 (is that symlink useful?) so that chroot setup
-    is more straight forward.
-  * /etc/icecast2/icecast.xml becomes a symlink to
-    /usr/share/icecast2/etc/icecast2/icecast.xml for the same reason.
-
- -- Rama   Fri, 30 Jun 2006 23:13:56 +0200
-
-icecast2 (2.3-kh6-1) stable; urgency=low
-
-  * Non-maintainer unofficial branch upload.
-  * Depends on libtheora > 1.0alpha6
-  * NOTE: this is not considered stable but rather experimental
-
- -- Rama   Thu, 25 May 2006 19:12:39 +0200
-
-icecast2 (2.2.0-1) unstable; urgency=low
-
-  * New upstream release. Closes: bug#286739 (thanks - again - to Andre
-    Tomt ).
-  * Debian subdir is again stripped from tarball, but autotools patching
-    (to aboid complaints about the missing dir) is now in diff. Updated
-    note in debian/copyright.
-  * Build-depend on libtheora-dev (current version is too old but as
-    soon as libtheora is updated it will then get built in).
-  * Updated source location in debian/copyright and debian/watch.
-  * Correct typo in long description.
-
- -- Jonas Smedegaard   Wed, 29 Dec 2004 15:04:17 +0100
-
-icecast2 (2.1.0-1) unstable; urgency=medium
-
-  * New upstream release. Closes: bug#279869 (thanks to Andre Tomt
-    ).
-  * Strip annoying debian subdir from upstream source.
-  * Update debian/copyright:
-    + License is included now (no need to refer to CVS).
-    + Remove stray repeated license above licensing section.
-    + Add note about tarball not being pristine (and explain why).
-    + Use capital "I" in initial introduction to upstream name.
-  * Use generic (but unofficial) buildinfo cdbs snippet.
-  * Drop cleaning up conf/icecast.xml.dist (handled properly upstream
-    now).
-  * Set urgency=medium to hopefully reach sarge.
-  * Correct README.Debian to mention user "icecast2" (not "icecast").
-  * Move and symlink stylesheet to /etc (similar to xslt files).
-
- -- Jonas Smedegaard   Sun,  7 Nov 2004 15:52:50 +0100
-
-icecast2 (2.0.2.debian-3) unstable; urgency=high
-
-  * Fix wrong space in build-depends.
-  * Set urgency=high to hopefully get this compiled (even for sparc)
-    in time for sarge release.
-
- -- Jonas Smedegaard   Tue, 26 Oct 2004 14:23:04 +0200
-
-icecast2 (2.0.2.debian-2) unstable; urgency=high
-
-  * Include "endscript" in logrotate rule. Closes: bug#274823 (thanks to
-    Jose Antonio ).
-  * Set urgency=high to still push earlier security fix.
-
- -- Jonas Smedegaard   Mon,  4 Oct 2004 11:00:31 +0200
-
-icecast2 (2.0.2.debian-1) unstable; urgency=high
-
-  * New upstream release.
-    + Fixes upstream announced security bug.
-    + Set urgency=high due to the above.
-    + Closes: bug#274320 (thanks to Jeroen Wolffelaar
-      ).
-    + Again - strip non-free win32/ResizableDialog.* from source.
-    + While we are at it, move upstream debian subdirectory off to
-      dist/debian. Hack configure and configure.in to not mess around.
-  * Update location of upstream source in copyright and watch file.
-  * Use more flexible regexp in watch file.
-  * Rename Debian NEWS file in source to get recognized automativally.
-  * Devine man page within rules file.
-  * Drop unneeded preinst (from a time before official Debian where it
-    did not run properly as a daemon?).
-  * Build-depend on libcurl3-dev, and on the virtual package libcurl-dev
-    only as fallback (not mandatory, but aptitude chokes and so will the
-    build daemons as well, I suppose).
-  * Reload (which does a sighup) daemon after logrotate. Closes:
-    bug#265301 (thanks to David Pashley ).
-
- -- Jonas Smedegaard   Sat,  2 Oct 2004 11:27:14 +0200
-
-icecast2 (2.0.1.debian-3) unstable; urgency=low
-
-  * Tolerate failure to remove icecast group on purge (it is used by
-    other packages as well, and was badly handled in older unstable
-    packages). This closes: Bug#246263 (thanks to Pete de Zwart
-    ).
-  * Fix logrotate script. Closes: Bug#249404, #255430 (thanks
-    to Julien Cristau  and Mykola A. Nickishov
-    ).
-  * Mention upstream website in long description.
-  * Build-depend on autotools-dev to let cdbs do clever autotools magic.
-  * Stylistic improvements to debian/rules:
-    + Add copyright notice and editor hints at top.
-    + Use only (cdbs-)generic make targets.
-  * Build-depend generically on libxslt-dev and libcurl-dev (instead of
-    libxslt1-dev and libcurl2-dev).
-
- -- Jonas Smedegaard   Wed,  7 Jul 2004 09:32:56 +0200
-
-icecast2 (2.0.1.debian-2) unstable; urgency=low
-
-  * Really add ChangeLog from 2.0.0.
-
- -- Jonas Smedegaard   Thu, 20 May 2004 23:03:22 +0200
-
-icecast2 (2.0.1.debian-1) unstable; urgency=medium
-
-  * New upstream release (thanks to Ian Kumlien ):
-    + According to announcement on website, it "fixes a overflow buffer
-      which can cause server crashes under certain circumstances" so set
-      urgency=medium (the code change is one line only).
-    + Again, remove the non-free win32/ResizableDialog.* as it is
-      still(!) distributed with official source.
-    + Add ChangeLog from 2.0.0 missing from current release.
-  * Register with (and recommend) logrotate. Closes: Bug#299404 (thanks
-    to Julien Cristau ).
-  * Add note to README.Debian about chroots not working with symlinks
-    due to FHS requirements of configuration files located below /etc.
-    Closes: Bug#250056 (thanks to Ian Kumlien ).
-  * Standards-Version: 3.6.1 (no changes needed).
-  * Explicitly note version in watch file, and add it to TODO.Debian.
-  * Add comment to watch file hinting on how to use it.
-
- -- Jonas Smedegaard   Thu, 20 May 2004 21:40:03 +0200
-
-icecast2 (2.0.0.debian-1) unstable; urgency=low
-
-  * Re-release with non-free files (unused with Debian) stripped from
-    source:
-    + Remove non-free win32/ResizableDialog.* from source, and remove
-      its copyright and licensing info from debian/copyright.
-    + Add to debian/copyright GPL info taken from newer CVS, and email
-      from upstream to BTS permitting it to be used also with this
-      earlier release.
-    + This closes: Bug#229720, thanks to upstream and Steve Langasek
-      .
-  * Add TODO.Debian with reminder to clean this mess later.
-
- -- Jonas Smedegaard   Sun, 28 Mar 2004 16:02:27 +0200
-
-icecast2 (2.0.0-2) unstable; urgency=low
-
-  * Add group if non-existing.
-
- -- Jonas Smedegaard   Mon, 26 Jan 2004 16:07:23 +0100
-
-icecast2 (2.0.0-1) unstable; urgency=low
-
-  * New upstream release. Closes: Bug#223645, thanks to Nicholas Humfrey
-    .
-  * Use upstream long description, and rearrange short description a
-    bit.
-  * Rewrite debian/copyright:
-    + Note the upstream package name.
-    + Drop Debian-related info also in debian/changelog.
-    + Update location of upstream source.
-    + Replace general copyright and license info (where was it found?
-      See bug#229720) with that of individual files where provided.
-  * Update debian/watch with new location.
-  * Use username icecast2 (instead of icecast also used in the package
-    icecast-server). Add NEWS.Debian with info on the change. Closes:
-    bug#215671, #226807, thanks to Michael Deegan
-     and Robin Lee Powell
-    .
-  * Make sure /etc/icecast2 is owned by icecast2 and not world readable.
-    Closes: bug#210860, thanks to Frank Barknecht .
-  * Install NEWS again (now that NEWS and ChangeLog are different).
-  * Build-depend on libcurl2-dev (again, and hope it works now...).
-    Closes: Bug#222274 thanks to Nicholas Humfrey .
-  * Let icecast2 go into background by itself (using -b). Closes:
-    Bug#204061 (and add the actual content of the bugreport - how to
-    bind to a priviledged port by starting as root - to README.Debian),
-    thanks to Jürgen A. Erhard .
-  * Let "configure --program-transform-name" rename icecast to icecast2.
-  * Let debhelper create /var/log/icecast2/.
-  * Keep debian/rules comments from showing during build.
-
- -- Jonas Smedegaard   Mon, 26 Jan 2004 06:30:26 +0100
-
-icecast2 (1.9+2.0beta3-1) unstable; urgency=low
-
-  * New upstream release.
-  * Taking over maintainership. When Keegan some day comes through the
-    NM process he can take over maintainance.
-  * ChangeLog is provided upstream now, so use that (in favor of NEWS).
-
- -- Jonas Smedegaard   Tue, 16 Dec 2003 22:02:25 +0100
-
-icecast2 (1.9+2.0alphasnap2+20030802-1.2) unstable; urgency=low
-
-  * Another sponsor-NMU (forgot to force including source).
-
- -- Jonas Smedegaard   Sun, 17 Aug 2003 10:25:16 +0200
-
-icecast2 (1.9+2.0alphasnap2+20030802-1.1) unstable; urgency=low
-
-  * NMU by sponsor.
-
- -- Jonas Smedegaard   Sun, 17 Aug 2003 01:25:22 +0200
-
-icecast2 (1.9+2.0alphasnap2+20030802-1) unstable; urgency=low
-
-  * Added a 'watch' file to automate tracking of updates.
-  * Now uses dh-buildinfo to store information about the package build
-    environment.  Added a Build-Dependancy to dh-buildinfo.
-  * Removed cdbs/autotools-vars.mk, from cdbs CVS, because a new release
-    was made.
-  * Enabled curl during configure stage, since --disable-curl was recently
-    broken upstream, and potentially broken YP support can be disabled at
-    runtime.
-  * Trimmed ancient upgrade nodes and other cruft, left over from
-    pre-Debian versions, from README.Debian.
-  * Removed some autotools build cruft, since bugs were fixed upstream.
-  * Normalized {preinst,postinst,postrm} filenames to
-    icecast2.{preinst,postinst,postrm}.  Thanks to Emmanuel le Chevoir
-    for this suggestion.
-  * Fixed preinst; was stopping /usr/bin/icecast instead of
-    /usr/bin/icecast2.  Thanks to Emmanuel le Chevoir for this suggestion.
-  * Removed prerm, since it was not serving any purpose.  Thanks to
-    Emmanuel le Chevoir for this suggestion.
-  * Cleaned up postinst: removed old comments, fixed a path typo in the
-    configuration file location change message.
-  * Cleaned up postrm: removed old comments, fixed a typo in the group
-    removal test.
-  * Thanks to Jonas Smedegaard for sponsoring this package, and
-    providing many good suggestions.
-
- -- Keegan Quinn   Sat,  2 Aug 2003 20:28:13 -0700
-
-icecast2 (1.9+2.0alphasnap2+20030720-1.1) unstable; urgency=low
-
-  * NMU by sponsor (still closes: Bug#178160).
-
- -- Jonas Smedegaard   Wed, 23 Jul 2003 06:03:42 +0200
-
-icecast2 (1.9+2.0alphasnap2+20030720-1) unstable; urgency=low
-
-  * New daily snapshot build.
-  * Added Build-Dependancy to cdbs, and increased debhelper version
-    requirement as recommended by cdbs README.
-  * Corrected Standards-Version to 3.6.0.  This package now generates
-    no lintian errors.
-  * Updated Recommends for ices to ices2; it was renamed.
-  * Added more information to the long description.
-  * Thanks to Jonas Smedegaard for sponsoring this package.
-  * This revision still closes: #178160 - the last was not uploaded.
-
- -- Keegan Quinn   Mon, 21 Jul 2003 08:55:27 -0700
-
-icecast2 (1.9+2.0alphasnap2+20030714-0.2) unstable; urgency=low
-
-  * Sponsored upload. Closes: Bug#178160.
-  * Switch to cdbs (agreed with maintainer).
-  * Use cdbs autotools-vars.mk from CVS to avoid cross-compiling on same
-    host.
-  * Add build-dependency on libxml2-dev.
-  * Explicitly configure without curl support to avoid building broken
-    YP stuff.
-  * Hack src/Makefile.am to use AM_CFLAGS instead of CFLAGS (which is
-    overridden by cdbs), and add clean rule to avoid invoking automake.
-  * Disable daemon by default and hint about changing passwords before
-    enabling.
-  * Avoid moving config files from pre-Debian times - instead just print
-    a warning if config exists in old location (better mess as little as
-    possible with files not ever claimed to be ours).
-  * Update README.Debian to reflect the above, include note about YP
-    support not compiled in, and remove note regarding adoption.
-  * Strip paths from packaging scripts (they may move around in the
-    future, and if PATH is wrong then something else broken anyway).
-  * Avoid removing unused /usr/share/icecast2 on purge.
-  * Remove icecast group on purge only if empty.
-  * Standards-version 3.6 (no changes needed).
-  * Fix wrong escaping of sed vars in conf/Makefile.
-  * Symlink public files from /usr/share/icecast2 to /etc/icecast2
-    (instead of pointing public root dirs below /etc).
-  * Use upstream config (paths are properly included now).
-
- -- Jonas Smedegaard   Sun, 20 Jul 2003 20:19:30 +0200
-
-icecast2 (1.9+2.0alphasnap2+20030714-0.1) unstable; urgency=low
-
-  * New daily snapshot build.
-  * Updated versioning scheme to reflect (as well as possible) that the 
-    source is a daily snapshot now, not CVS.
-
- -- Keegan Quinn   Mon, 14 Jul 2003 19:39:58 -0700
-
-icecast2 (1.9+2.0alphacvs030704-0.1) unstable; urgency=low
-
-  * Constructed a build script to completely automate the construction
-    of the 'pristine' tarball from CVS.  This doesn't really effect the
-    contents of the package, just makes it easier for me to rebuild.
-  * New CVS source.
-  * Removed Build-Dependancy on libcurl2-dev; packages built without this
-    library present will not have YP functionality, which is okay for
-    now since it's badly broken.
-  * Updated the default configuration file, including some new options
-    recently added upstream.
-  * Added a number of tweaks to clean up and rearrange new configuration
-    and documentation added to upstream install target.
-  * Moved the configuration file from /etc/icecast.xml to
-    /etc/icecast2/icecast.xml.  See README.Debian.
-  * Nice ugly version number to reflect that upstream calls this the 2.0
-    alpha branch, without potentially introducing the need for an epoch.
-
- -- Keegan Quinn   Thu,  3 Jul 2003 23:46:56 -0700
-
-icecast2 (0.00.cvs030529-0.1) unstable; urgency=low
-
-  * New CVS source.
-  * Removed unnecessary debconf stuff.
-  * Added README.Debian.
-  * Path updates:
-    - /usr/share/icecast to /usr/share/icecast2,
-    - /var/log/icecast to /var/log/icecast2,
-    - /usr/bin/icecast to /usr/bin/icecast2,
-    - /usr/share/man/man8/icecast.8.gz to /usr/share/man/man8/icecast2.8.gz.
-
- -- Keegan Quinn   Wed, 29 May 2003 22:53:21 -0700
-
-icecast2 (0.00.cvs030403-0.2) unstable; urgency=low
-
-  * Tried to make the default configuration more understandable. 
-
- -- Keegan Quinn   Fri,  4 Apr 2003 10:55:27 -0800
-
-icecast2 (0.00.cvs030403-0.1) unstable; urgency=low
-
-  * New CVS source.
-  * Minor changes to postrm.
-
- -- Keegan Quinn   Thu,  3 Apr 2003 16:05:09 -0800
-
-icecast2 (0.00.cvs030401-0.7) unstable; urgency=low
-
-  * Minor changes to postinst.
-  * Added --background flag to initscript, since this version of icecast
-    does not yet run detached.
-
- -- Keegan Quinn   Thu,  3 Apr 2003 14:24:19 -0800
-
-icecast2 (0.00.cvs030401-0.6) unstable; urgency=low
-
-  * Added Debianized configuration file.
-  * Created and set ownership of /var/log/icecast and /usr/share/icecast.
-
- -- Keegan Quinn   Thu,  3 Apr 2003 14:15:11 -0800
-
-icecast2 (0.00.cvs030401-0.5) unstable; urgency=low
-
-  * Attempt at making debconf work properly.
-
- -- Keegan Quinn   Thu,  3 Apr 2003 12:07:16 -0800
-
-icecast2 (0.00.cvs030401-0.4) unstable; urgency=low
-
-  * Minor edits to init.d script.
-  * Added bits to create and remove system accounts appropriately.
-  * Typo fix in the manual page.
-
- -- Keegan Quinn   Thu,  3 Apr 2003 11:06:48 -0800
-
-icecast2 (0.00.cvs030401-0.3) unstable; urgency=low
-
-  * Finished init.d script and manual page.
-  * Updated postinst to handle rc*.d links.
-  * Package is now lintian/linda clean.
-
- -- Keegan Quinn   Wed,  2 Apr 2003 16:29:18 -0800
-
-icecast2 (0.00.cvs030401-0.2) unstable; urgency=low
-
-  * Updated copyright (replacing dh_make template).
-  * Fixed duplicate conffiles.
-
- -- Keegan Quinn   Wed,  2 Apr 2003 16:18:02 -0800
-
-icecast2 (0.00.cvs030401-0.1) unstable; urgency=low
-
-  * New CVS source.
-  * Lots of packaging cleanup.
-  * Initial stab at manual page and init.d script.
-
- -- Keegan Quinn   Wed,  2 Apr 2003 10:25:56 -0800
-
-icecast2 (0.00.cvs030320-0.1) unstable; urgency=low
-
-  * New CVS source.
-  * Automated CVS original source creation.
-
- -- Keegan Quinn   Thu, 20 Mar 2003 12:58:49 -0800
-
-icecast2 (0.00.cvs030315-0.1) unstable; urgency=low
-
-  * Initial Release.
-
- -- Keegan Quinn   Sun, 16 Mar 2003 13:45:23 -0800
diff --git a/debian/compat b/debian/compat
deleted file mode 100644
index b8626c4c..00000000
--- a/debian/compat
+++ /dev/null
@@ -1 +0,0 @@
-4
diff --git a/debian/control b/debian/control
deleted file mode 100644
index 18d2b974..00000000
--- a/debian/control
+++ /dev/null
@@ -1,20 +0,0 @@
-Source: icecast2
-Section: sound
-Priority: optional
-Maintainer: Rama 
-Build-Depends: cdbs, autotools-dev, debhelper (>> 4.1.0), dh-buildinfo, libogg-dev (>> 1.0.0), libvorbis-dev (>> 1.0.0), libxslt-dev, libxml2-dev, libcurl3-dev | libcurl-dev, libtheora-dev (>= 0.0.0.alpha6)
-Standards-Version: 3.6.1
-
-Package: icecast2
-Architecture: any
-Depends: ${shlibs:Depends}
-Recommends: ices2
-Description: Ogg Vorbis and MP3 streaming media server
- Icecast is a streaming media server which currently supports Ogg 
- Vorbis and MP3 audio streams. It can be used to create an Internet 
- radio station or a privately running jukebox and many things in 
- between. It is very versatile in that new formats can be added 
- relatively easily and supports open standards for communication and 
- interaction.
- .
- Website: http://www.icecast.org/
diff --git a/debian/copyright b/debian/copyright
deleted file mode 100644
index ebcba318..00000000
--- a/debian/copyright
+++ /dev/null
@@ -1,96 +0,0 @@
-This is Icecast 2.x packaged for Debian.
-
-Upstream source: http://downloads.us.xiph.org/releases/icecast/
-
-Note: Tarball distributed with Debian is currently not pristine: the
-subdir "debian" has been stripped to not clash with the official Debian
-packaging files.
-
-Upstream Authors: the icecast team 
-
-Copyright and license; src/httpd/:
-
-  licensed under the lgpl
-  
-  created by jack moffitt 
-
-Copyright and license; src/avl/:
-
- * Copyright (C) 1995-1997 by Sam Rushing 
- * 
- *                         All Rights Reserved
- * 
- * Permission to use, copy, modify, and distribute this software and
- * its documentation for any purpose and without fee is hereby
- * granted, provided that the above copyright notice appear in all
- * copies and that both that copyright notice and this permission
- * notice appear in supporting documentation, and that the name of Sam
- * Rushing not be used in advertising or publicity pertaining to
- * distribution of the software without specific, written prior
- * permission.
- * 
- * SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
- * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
- * NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
- * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
- * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
- * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
- * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-Copyright and license; src/thread/:
-
- * Copyright (c) 1999, 2000 the icecast team 
- *
- *  This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Library General Public
- *  License as published by the Free Software Foundation; either
- *  version 2 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
- *  Library General Public License for more details.
- *
- *  You should have received a copy of the GNU Library General Public
- *  License along with this library; if not, write to the Free
- *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-Copyright and license; src/net/:
-
- * Copyright (C) 1999 the icecast team 
- *
- *  This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Library General Public
- *  License as published by the Free Software Foundation; either
- *  version 2 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
- *  Library General Public License for more details.
- *
- *  You should have received a copy of the GNU Library General Public
- *  License along with this library; if not, write to the Free
- *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-Copyright and license; src/httpd/:
-
-  lgpl
-  
-  by jack moffitt 
-
-Copyright and license; other files (found in src/client.c):
-
- * This program is distributed under the GNU General Public License, version 2.
- * A copy of this license is included with this source.
- *
- * Copyright 2000-2004, Jack Moffitt ,
- *                      oddsock ,
- *                      Karl Heyes 
- *                      and others (see AUTHORS for details).
-
-
-On Debian systems, the complete text of both the GNU General Public
-License (GPL) and the GNU Library General Public License (LGPL) can be
-found below `/usr/share/common-licenses/'.
diff --git a/debian/icecast2.1 b/debian/icecast2.1
deleted file mode 100644
index dc1b9e90..00000000
--- a/debian/icecast2.1
+++ /dev/null
@@ -1,24 +0,0 @@
-.\"                                      Hey, EMACS: -*- nroff -*-
-.TH ICECAST2 1 "July 28, 2003"
-.SH NAME
-icecast2 \- an MP3/Ogg Vorbis broadcast streaming media server
-.SH SYNOPSIS
-.B icecast2
--c 
-.RI config.xml
-.SH DESCRIPTION
-\fBicecast2\fP is an audio broadcasting system that streams music in
-Ogg Vorbis and/or MPEG 1 Layer III format.  It accepts stream input
-from sources like ices0 and ices2, and broadcasts to clients like xmms.
-.SH OPTIONS
-\fBicecast2\fP has no command line options, except to specify the location
-of an XML configuration file.  All operational aspects of the software
-are controlled by this XML configuration file.
-.SH SEE ALSO
-The example configuration files, provided with the package documentation
-are the only reliable source of information on this software.
-It is still under very active development;
-documentation will be written when it is possible to do so.
-.SH AUTHOR
-icecast2 was created by the icecast team .
-This manual page was written by Keegan Quinn .
diff --git a/debian/icecast2.default b/debian/icecast2.default
deleted file mode 100644
index d45f5d07..00000000
--- a/debian/icecast2.default
+++ /dev/null
@@ -1,19 +0,0 @@
-# Defaults for icecast2 initscript
-# sourced by /etc/init.d/icecast2
-# installed at /etc/default/icecast2 by the maintainer scripts
-
-#
-# This is a POSIX shell fragment
-#
-
-# Full path to the server configuration file
-CONFIGFILE="/etc/icecast2/icecast.xml"
-
-# Name or ID of the user and group the daemon should run under
-USERID=icecast2
-GROUPID=icecast
-
-# Edit /etc/icecast2/icecast.xml and change at least the passwords.
-# Change this to true when done to enable the init.d script
-ENABLE=false
-
diff --git a/debian/icecast2.init b/debian/icecast2.init
deleted file mode 100644
index 6bb85d50..00000000
--- a/debian/icecast2.init
+++ /dev/null
@@ -1,67 +0,0 @@
-#! /bin/sh
-#
-# icecast2
-#
-#		Written by Miquel van Smoorenburg .
-#		Modified for Debian 
-#		by Ian Murdock .
-#
-#		Further modified by Keegan Quinn 
-#		for use with Icecast 2
-#
-
-PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
-DAEMON=/usr/bin/icecast2
-NAME=icecast2
-DESC=icecast2
-
-test -x $DAEMON || exit 0
-
-# Defaults
-CONFIGFILE="/etc/icecast2/icecast.xml"
-CONFIGDEFAULTFILE="/etc/default/icecast2"
-USERID=icecast2
-GROUPID=icecast
-ENABLE="false"
-
-# Reads config file (will override defaults above)
-[ -r "$CONFIGDEFAULTFILE" ] && . $CONFIGDEFAULTFILE
-
-if [ "$ENABLE" != "true" ]; then
-	echo "$NAME daemon disabled - read $CONFIGDEFAULTFILE."
-	exit 0
-fi
-
-set -e
-
-case "$1" in
-  start)
-	echo -n "Starting $DESC: "
-	start-stop-daemon --start --quiet --chuid $USERID:$GROUPID \
-		--exec $DAEMON -- -b -c $CONFIGFILE
-	echo "$NAME."
-	;;
-  stop)
-	echo -n "Stopping $DESC: "
-	start-stop-daemon --stop --oknodo --quiet --exec $DAEMON
-	echo "$NAME."
-	;;
-  reload|force-reload)
-	echo "Reloading $DESC configuration files."
-	start-stop-daemon --stop --signal 1 --quiet --exec $DAEMON
-	;;
-  restart)
-	echo -n "Restarting $DESC: "
-	start-stop-daemon --stop --oknodo --quiet --exec $DAEMON
-	sleep 2
-	start-stop-daemon --start --quiet --chuid $USERID:$GROUPID \
-		--exec $DAEMON -- -b -c $CONFIGFILE
-	echo "$NAME."
-	;;
-  *)
-	echo "Usage: $0 {start|stop|restart|reload|force-reload}" >&2
-	exit 1
-	;;
-esac
-
-exit 0
diff --git a/debian/icecast2.manpages b/debian/icecast2.manpages
deleted file mode 100644
index 69e59afa..00000000
--- a/debian/icecast2.manpages
+++ /dev/null
@@ -1 +0,0 @@
-debian/icecast2.1
diff --git a/debian/icecast2.postinst b/debian/icecast2.postinst
deleted file mode 100644
index 4476a6fe..00000000
--- a/debian/icecast2.postinst
+++ /dev/null
@@ -1,55 +0,0 @@
-#! /bin/sh
-# postinst script for icecast2
-
-set -e
-
-case "$1" in
-    configure)
-
-    ;;
-
-    abort-upgrade|abort-remove|abort-deconfigure)
-	exit 0
-    ;;
-
-    *)
-        echo "postinst called with unknown argument \`$1'" >&2
-        exit 1
-    ;;
-esac
-
-# Move configuration file to current location, if an old one exists
-# and the init.d script configuration file was updated
-if [ -f /etc/icecast.xml ] && grep -q /etc/icecast2/ /etc/default/icecast2; then
-    echo "It seems you have an old configuration lying around at"
-    echo "/etc/icecast.xml. You will need to manually merge with"
-    echo "the current configuration at /etc/icecast2/icecast.xml."
-    echo
-    echo "See /usr/share/doc/icecast2/examples for new configuration options."
-fi
-
-if [ -f /etc/icecast2/icecast.xml ] ; then
-	echo "* Found existing /etc/icecast2/icecast.xml"
-	echo "* Moving /etc/icecast2/icecast.xml to /usr/share/icecast2/etc/icecast2"
-	mv /etc/icecast2/icecast.xml /usr/share/icecast2/etc/icecast2
-	ln -s /usr/share/icecast2/etc/icecast2/icecast.xml /etc/icecast2/icecast.xml 
-fi
-
-if ! getent group icecast >/dev/null 2>&1; then
-    addgroup --system icecast
-fi
-
-# Check for an account named 'icecast2'
-if ! id icecast2 >/dev/null 2>&1; then
-    # Create the new system account
-    adduser --system --disabled-password --disabled-login \
-	--home /usr/share/icecast2 --no-create-home --ingroup icecast icecast2
-fi
-
-chown -R icecast2: /var/log/icecast2 /etc/icecast2 /usr/share/icecast2
-chmod -R ug=rw,o=,ug+X /etc/icecast2
-
-#DEBHELPER#
-
-exit 0
-
diff --git a/debian/icecast2.postrm b/debian/icecast2.postrm
deleted file mode 100644
index 33808d81..00000000
--- a/debian/icecast2.postrm
+++ /dev/null
@@ -1,30 +0,0 @@
-#! /bin/sh
-# postrm script for icecast2
-
-set -e
-
-case "$1" in
-    purge)
-	rm -rf /var/log/icecast2
-	rm -rf /usr/share/icecast2/log
-
-	if id icecast2 >/dev/null 2>&1; then
-		deluser icecast2
-	fi
-
-	# Remove group only if empty
-	if getent group icecast | awk -F: ' { print $4 } ' | egrep -cq '^$'; then
-		groupdel icecast || echo "Error occured removing group icecast, please do it manually."
-	fi
-	;;
-    remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
-
-	;;
-    *)
-	echo "postrm called with unknown argument \`$1'" >&2
-	exit 1
-esac
-
-#DEBHELPER#
-
-exit 0
diff --git a/debian/icecast2.preinst b/debian/icecast2.preinst
deleted file mode 100644
index 35e111eb..00000000
--- a/debian/icecast2.preinst
+++ /dev/null
@@ -1,27 +0,0 @@
-#! /bin/sh
-# preinst script for icecast2
-
-set -e
-
-case "$1" in
-    install|upgrade)
-        if [ "$1" = "upgrade" ]
-        then
-            start-stop-daemon --stop --quiet --oknodo  \
-                --exec /usr/bin/icecast2 2>/dev/null || true
-        fi
-    ;;
-
-    abort-upgrade)
-    ;;
-
-    *)
-        echo "preinst called with unknown argument \`$1'" >&2
-        exit 1
-    ;;
-esac
-
-#DEBHELPER#
-
-exit 0
-
diff --git a/debian/rules b/debian/rules
deleted file mode 100755
index c36f91a1..00000000
--- a/debian/rules
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/make -f
-# -*- mode: makefile; coding: utf-8 -*-
-# Copyright © 2004 Jonas Smedegaard 
-include /usr/share/cdbs/1/rules/debhelper.mk
-include /usr/share/cdbs/1/class/autotools.mk
-include debian/cdbs/1/rules/buildinfo.mk
-
-DEB_CONFIGURE_SYSCONFDIR = /etc/icecast2
-DEB_CONFIGURE_EXTRA_FLAGS = --program-transform-name="s/icecast$$/icecast2/"
-DEB_MAKE_INVOKE += PACKAGE=icecast2 docdir=/usr/share/doc/icecast2 pkgdatadir=/usr/share/icecast2
-DEB_INSTALL_DIRS_icecast2 = var/log/icecast2
-DEB_INSTALL_MANPAGES_icecast2 = debian/icecast2.1
-
-# Debian has a central copy of the GPL, no need to distribute again
-common-binary-post-install-arch::
-	rm -f $(DEB_DESTDIR)/usr/share/doc/icecast2/COPYING
-
-# Move XSLT templates and CSS files to /etc and replace with symlinks
-common-binary-post-install-arch::
-	for file in `cd $(DEB_DESTDIR)/usr/share && find icecast2 -type d \( -name admin -or -name web \)`; do \
-		ln -s /usr/share/$$file $(DEB_DESTDIR)/etc/$$file; \
-	done
-	mkdir $(DEB_DESTDIR)/usr/share/icecast2/log
-	chown icecast2:icecast $(DEB_DESTDIR)/usr/share/icecast2/log
-	mkdir -p $(DEB_DESTDIR)/usr/share/icecast2/etc/icecast2
-	#mv $(DEB_DESTDIR)/etc/icecast2/icecast.xml $(DEB_DESTDIR)/usr/share/icecast2/etc/icecast2
-	#ln -s /usr/share/icecast2/etc/icecast2/icecast.xml $(DEB_DESTDIR)/etc/icecast2/icecast.xml 
diff --git a/debian/watch b/debian/watch
deleted file mode 100644
index 5acd78dc..00000000
--- a/debian/watch
+++ /dev/null
@@ -1,3 +0,0 @@
-# Run the "uscan" command to check for upstream updates and more.
-version=2
-http://downloads.us.xiph.org/releases/icecast/icecast-([\d+\.]+|\d+)(\.tar|\.tgz)(\.gz|\.bz2|) 2.2.0 uupdate
diff --git a/src/admin.c b/src/admin.c
index 51111a34..c3cee47f 100644
--- a/src/admin.c
+++ b/src/admin.c
@@ -58,6 +58,7 @@ static void command_reset_stats (client_t *client, source_t *source, int respons
 static void command_manageauth(client_t *client, source_t *source,
         int response);
 static void command_buildm3u(client_t *client, const char *mount);
+static void command_show_image (client_t *client, const char* mount);
 static void command_kill_source(client_t *client, source_t *source,
         int response);
 static void command_updatemetadata(client_t *client, source_t *source,
@@ -355,6 +356,11 @@ int admin_handle_request (client_t *client, const char *uri)
             command_buildm3u (client, mount);
             return 0;
         }
+        if (strcmp (uri, "showimage") == 0)
+        {
+            command_show_image (client, mount);
+            return 0;
+        }
 
         /* This is a mount request, but admin user is allowed */
         if (client->authenticated == 0)
@@ -763,6 +769,29 @@ static void command_show_listeners(client_t *client, source_t *source,
     xmlFreeDoc(doc);
 }
 
+static void command_show_image (client_t *client, const char *mount)
+{
+    source_t *source;
+
+    avl_tree_rlock (global.source_tree);
+    source = source_find_mount_raw (mount);
+    if (source && source->format && source->format->get_image)
+    {
+        thread_mutex_lock (&source->lock);
+        avl_tree_unlock (global.source_tree);
+        if (source->format->get_image (client, source->format) == 0)
+        {
+            thread_mutex_unlock (&source->lock);
+            fserve_add_client (client, NULL);
+            return;
+        }
+        thread_mutex_unlock (&source->lock);
+    }
+    else
+        avl_tree_unlock (global.source_tree);
+    client_send_404 (client, "No image available");
+}
+
 static void command_buildm3u (client_t *client, const char *mount)
 {
     const char *username = NULL;
@@ -961,6 +990,7 @@ static void command_metadata(client_t *client, source_t *source,
     format_plugin_t *plugin;
     xmlDocPtr doc;
     xmlNodePtr node;
+    int same_ip = 1;
 
     doc = xmlNewDoc(XMLSTR("1.0"));
     node = xmlNewDocNode(doc, NULL, XMLSTR("iceresponse"), NULL);
@@ -977,10 +1007,13 @@ static void command_metadata(client_t *client, source_t *source,
     thread_mutex_lock (&source->lock);
 
     plugin = source->format;
+    if (source->client && strcmp (client->con->ip, source->client->con->ip) != 0)
+        if (response == RAW && connection_check_admin_pass (client->parser) == 0)
+            same_ip = 0;
 
     do
     {
-        if (plugin == NULL)
+        if (same_ip == 0 && plugin == NULL)
             break;
         if (artwork)
             stats_event (source->mount, "artwork", artwork);
@@ -1026,6 +1059,7 @@ static void command_shoutcast_metadata(client_t *client, source_t *source)
 {
     const char *action;
     const char *value;
+    int same_ip = 1;
 
     DEBUG0("Got shoutcast metadata update request");
 
@@ -1046,6 +1080,10 @@ static void command_shoutcast_metadata(client_t *client, source_t *source)
         return;
     }
 
+    if (source->client && strcmp (client->con->ip, source->client->con->ip) != 0)
+        if (connection_check_admin_pass (client->parser) == 0)
+            same_ip = 0;
+
     if (source->format && source->format->set_tag)
     {
         httpp_set_query_param (client->parser, "mount", client->server_conn->shoutcast_mount);
@@ -1084,7 +1122,7 @@ static void command_stats (client_t *client, const char *filename)
 
     show_mount = httpp_get_query_param (client->parser, "mount");
 
-    stats_get_xml(&doc, 1, show_mount);
+    stats_get_xml(&doc, STATS_ALL, show_mount);
     admin_send_response (doc, client, response, filename);
     xmlFreeDoc(doc);
 }
diff --git a/src/auth_htpasswd.c b/src/auth_htpasswd.c
index 9bf9f0b4..638896fd 100644
--- a/src/auth_htpasswd.c
+++ b/src/auth_htpasswd.c
@@ -108,6 +108,8 @@ static void htpasswd_recheckfile (htpasswd_auth_state *htpasswd)
     char *sep;
     char line [MAX_LINE_LEN];
 
+    if (htpasswd->filename == NULL)
+        return;
     if (stat (htpasswd->filename, &file_stat) < 0)
     {
         WARN1 ("failed to check status of %s", htpasswd->filename);
diff --git a/src/cfgfile.h b/src/cfgfile.h
index 92f10d9d..25b0a448 100644
--- a/src/cfgfile.h
+++ b/src/cfgfile.h
@@ -132,6 +132,14 @@ struct _listener_t
     int ssl;
 };
 
+typedef struct
+{
+    char *hostname;
+    int  port;
+    char *username;
+    char *password;
+} ice_master_details;
+
 typedef struct ice_config_tag
 {
     char *config_filename;
@@ -169,6 +177,8 @@ typedef struct ice_config_tag
     listener_t *listen_sock;
     unsigned int listen_sock_count;
 
+    ice_master_details *master;
+
     char *master_server;
     int master_server_port;
     int master_update_interval;
diff --git a/src/connection.c b/src/connection.c
index 614cbfaa..eaf3036c 100644
--- a/src/connection.c
+++ b/src/connection.c
@@ -1129,7 +1129,7 @@ static void _handle_stats_request (client_t *client, char *uri)
 
     client->respcode = 200;
     snprintf (client->refbuf->data, PER_CLIENT_REFBUF_SIZE,
-            "HTTP/1.0 200 OK\r\n\r\n");
+            "HTTP/1.0 200 OK\r\ncapability: streamlist\r\n\r\n");
     client->refbuf->len = strlen (client->refbuf->data);
     fserve_add_client_callback (client, stats_callback, NULL);
 }
diff --git a/src/format.c b/src/format.c
index 2c2f82d5..7b18cd5f 100644
--- a/src/format.c
+++ b/src/format.c
@@ -378,4 +378,3 @@ static int format_prepare_headers (source_t *source, client_t *client)
     return 0;
 }
 
-
diff --git a/src/format.h b/src/format.h
index 24e59e9f..7a715c1d 100644
--- a/src/format.h
+++ b/src/format.h
@@ -53,6 +53,7 @@ typedef struct _format_plugin_tag
     void (*set_tag)(struct _format_plugin_tag *plugin, const char *tag, const char *value, const char *charset);
     void (*free_plugin)(struct _format_plugin_tag *self);
     void (*apply_settings)(client_t *client, struct _format_plugin_tag *format, struct _mount_proxy *mount);
+    int (*get_image)(client_t *client, struct _format_plugin_tag *format);
 
     /* for internal state management */
     void *_state;
diff --git a/src/format_kate.c b/src/format_kate.c
index 5f9e93d0..73a60ff8 100644
--- a/src/format_kate.c
+++ b/src/format_kate.c
@@ -38,12 +38,12 @@ typedef struct source_tag source_t;
 
 typedef struct _kate_codec_tag
 {
-    int             headers_done;
+    unsigned int    headers_done;
 #ifdef HAVE_KATE
     kate_info       ki;
     kate_comment    kc;
 #endif
-    int             num_headers;
+    unsigned int    num_headers;
     int             granule_shift;
     ogg_int64_t     last_iframe;
     ogg_int64_t     prev_granulepos;
diff --git a/src/format_mp3.c b/src/format_mp3.c
index 3eec8b0e..cb9ec072 100644
--- a/src/format_mp3.c
+++ b/src/format_mp3.c
@@ -484,12 +484,12 @@ static int complete_read (source_t *source)
             }
             return 0;
         }
+        rate_add (format->in_bitrate, bytes, global.time);
     }
     source_mp3->read_count += bytes;
     refbuf = source_mp3->read_data;
     refbuf->len = source_mp3->read_count;
     format->read_bytes += bytes;
-    rate_add (format->in_bitrate, bytes, global.time);
 
     if (source_mp3->read_count < source_mp3->queue_block_size)
     {
@@ -586,7 +586,6 @@ static refbuf_t *mp3_get_filter_meta (source_t *source)
                     sizeof (source_mp3->build_metadata));
             source_mp3->build_metadata_offset = 0;
             source_mp3->build_metadata_len = 1 + (*src * 16);
-            rate_add (plugin->in_bitrate, source_mp3->build_metadata_len, global.time);
         }
 
         /* do we have all of the metatdata block */
diff --git a/src/format_ogg.c b/src/format_ogg.c
index ffc33be9..251d4582 100644
--- a/src/format_ogg.c
+++ b/src/format_ogg.c
@@ -54,6 +54,7 @@ static void format_ogg_free_plugin (format_plugin_t *plugin);
 static int  create_ogg_client_data(source_t *source, client_t *client);
 static void free_ogg_client_data (client_t *client);
 
+static int get_image (client_t *client, struct _format_plugin_tag *format);
 static void write_ogg_to_file (struct source_tag *source, refbuf_t *refbuf);
 static refbuf_t *ogg_get_buffer (source_t *source);
 static int write_buf_to_client (client_t *client);
@@ -152,8 +153,7 @@ static void free_ogg_codecs (ogg_state_t *ogg_info)
     while (codec)
     {
         ogg_codec_t *next = codec->next;
-        if (codec->possible_start)
-            refbuf_release (codec->possible_start);
+        refbuf_release (codec->possible_start);
         codec->codec_free (ogg_info, codec);
         codec = next;
     }
@@ -177,6 +177,7 @@ int format_ogg_get_plugin (source_t *source)
     plugin->write_buf_to_file = write_ogg_to_file;
     plugin->create_client_data = create_ogg_client_data;
     plugin->free_plugin = format_ogg_free_plugin;
+    plugin->get_image = get_image;
     plugin->set_tag = NULL;
     plugin->apply_settings = apply_ogg_settings;
     if (strcmp (httpp_getvar (source->parser, "content-type"), "application/x-ogg") == 0)
@@ -623,4 +624,25 @@ static void write_ogg_to_file (struct source_tag *source, refbuf_t *refbuf)
     write_ogg_data (source, refbuf);
 }
 
+static int get_image (client_t *client, struct _format_plugin_tag *format)
+{
+    const char *serialp = httpp_get_query_param (client->parser, "serial");
+    ogg_state_t *ogg_info = format->_state;
+    ogg_codec_t *codec = ogg_info->codecs;
+    long serial;
 
+    if (serialp)
+        serial = atoll (serialp);
+    while (codec)
+    {
+        if (serialp == NULL || serial == codec->os.serialno)
+        {
+            int ret = 0;
+            if (codec->get_image)
+                ret = codec->get_image (client, codec);
+            return ret;
+        }
+        codec = codec->next;
+    }
+    return 0;
+}
diff --git a/src/format_ogg.h b/src/format_ogg.h
index 6320bab9..f22d6e63 100644
--- a/src/format_ogg.h
+++ b/src/format_ogg.h
@@ -65,6 +65,7 @@ typedef struct ogg_codec_tag
     refbuf_t *(*process_page)(ogg_state_t *ogg_info,
             struct ogg_codec_tag *codec, ogg_page *page);
     void (*codec_free)(ogg_state_t *ogg_info, struct ogg_codec_tag *codec);
+    int  (*get_image)(client_t *client, struct ogg_codec_tag *codec);
 } ogg_codec_t;
 
 
diff --git a/src/format_theora.c b/src/format_theora.c
index f1b4db58..1149f6bf 100644
--- a/src/format_theora.c
+++ b/src/format_theora.c
@@ -31,7 +31,20 @@ typedef struct source_tag source_t;
 
 #define CATMODULE "format-theora"
 #include "logging.h"
+#ifdef WITH_VIDEO_PREVIEW
+#include 
 
+typedef struct _video_preview_struct
+{
+	png_byte*   rgb_image                     ;
+	int         png_compression_level         ;
+
+	int         video_width                   ;
+	int         video_height                  ;
+	int         x_crop_offset                 ;
+	int         y_crop_offset                 ;
+} video_preview_t;
+#endif
 
 typedef struct _theora_codec_tag
 {
@@ -40,9 +53,255 @@ typedef struct _theora_codec_tag
     int             granule_shift;
     ogg_int64_t     last_iframe;
     ogg_int64_t     prev_granulepos;
+#ifdef WITH_VIDEO_PREVIEW
+    theora_state    td;
+    video_preview_t *video_preview;
+    int            frame_count;
+#endif
 } theora_codec_t;
 
 
+ /* video_preview.c 
+ * Copyright 2005, Silvano Galliani aka kysucix 
+ */
+
+
+#include "logging.h"
+
+
+#ifdef WITH_VIDEO_PREVIEW
+struct preview_details
+{
+    unsigned int total_length;
+    refbuf_t **last;
+};
+
+static void yuv2rgb (yuv_buffer *_yuv, video_preview_t *video_preview);
+static int clip (int x);
+
+
+static void user_write_data (png_structp png_ptr, png_bytep data, png_size_t length)
+{
+#define BUFFER_BLOCK_SZ 316*1024
+    struct preview_details *details = png_get_io_ptr (png_ptr);
+    unsigned int offset = 0;
+    int c = 0;
+
+    if (*details->last == NULL)
+    {
+        /* special case */
+        refbuf_t *r = refbuf_new (BUFFER_BLOCK_SZ);
+        r->len = 0;
+        *details->last = r;
+        c++;
+    }
+    while (length)
+    {
+        unsigned int amount;
+        refbuf_t *buffer = *details->last;
+        if (buffer->len == BUFFER_BLOCK_SZ)
+        {
+            refbuf_t *last = buffer;
+            buffer = refbuf_new (BUFFER_BLOCK_SZ);
+            buffer->len = 0;
+            last->next = buffer;
+            details->last = &last->next;
+            c++;
+        }
+        amount = BUFFER_BLOCK_SZ - buffer->len;
+        if (amount > length)
+            amount = length;
+        memcpy (buffer->data+buffer->len, data+offset, amount);
+        buffer->len += amount;
+        offset += amount;
+        length -= amount;
+    }
+    details->total_length += offset;
+}
+
+
+static void user_flush_data (png_structp png_ptr)
+{
+}
+
+static void user_error (png_structp png_ptr, png_const_charp c)
+{
+    longjmp (png_ptr->jmpbuf, 1);
+}
+
+
+void free_video_preview (video_preview_t *video_preview)
+{
+    DEBUG0 ("freeing video preview");
+    free (video_preview->rgb_image);
+    free (video_preview);
+}
+
+
+video_preview_t *init_video_preview (int width, int height, int _x_crop_offset, int _y_crop_offset) 
+{
+    DEBUG0("init video preview");
+
+    video_preview_t *video_preview =  calloc (1, sizeof (video_preview_t));
+
+    /* init structure */
+    video_preview -> rgb_image              = NULL;
+    video_preview -> png_compression_level  = Z_BEST_SPEED;
+
+    video_preview -> video_width            = width;
+    video_preview -> video_height           = height;
+    video_preview -> x_crop_offset          = _x_crop_offset;
+    video_preview -> y_crop_offset          = _y_crop_offset;
+
+    /* malloc rgb image */
+    video_preview -> rgb_image = (png_byte *)calloc (1, video_preview -> video_width * video_preview -> video_height * 4 );
+    if (video_preview->rgb_image == NULL)
+    {
+        ERROR0 ("Can't allocate memory for rgb_image");
+        free_video_preview (video_preview);
+        return NULL;
+    }
+    return video_preview;
+}
+
+
+static int write_video_preview (client_t *client, video_preview_t *video_preview)
+{
+    int i;
+    png_structp png_ptr = NULL;
+    png_infop info_ptr = NULL;
+    struct preview_details preview;
+
+    preview.total_length = 0;
+    preview.last = &client->refbuf->next;
+
+    png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, (png_voidp)NULL, NULL, NULL);
+    if (png_ptr == NULL)
+        return -1;
+
+    info_ptr = png_create_info_struct (png_ptr);
+    if (info_ptr == NULL)
+    {
+        png_destroy_write_struct (&png_ptr, (png_infopp)NULL);
+        return -1;
+    }
+
+    if (setjmp (png_ptr->jmpbuf))
+    {
+        png_destroy_write_struct (&png_ptr, (png_infopp)NULL);
+        return -1;
+    }
+
+    png_set_error_fn (png_ptr, NULL, user_error, user_error);
+    png_set_write_fn (png_ptr, &preview, user_write_data, user_flush_data);
+
+    png_set_filter( png_ptr, 0, PNG_FILTER_NONE );
+
+    /* set the zlib compression level */
+    png_set_compression_level (png_ptr, video_preview->png_compression_level);
+
+    png_set_IHDR (png_ptr, info_ptr, video_preview->video_width , video_preview->video_height,
+            8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE,
+            PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+
+    png_write_info (png_ptr, info_ptr);
+    ERROR0("finished writing png header");
+
+    /* write image to hard disk */
+    for ( i = 0; i < video_preview -> video_height; i++)
+        png_write_row (png_ptr, 
+                video_preview->rgb_image + i * (video_preview->video_width) * 4 );
+
+    png_write_end (png_ptr, info_ptr);
+    png_destroy_write_struct (&png_ptr, &info_ptr);
+
+    snprintf (client->refbuf->data, PER_CLIENT_REFBUF_SIZE, "HTTP/1.0 200 OK\r\n"
+            "Content-Length: %d\r\nContentType: image/png\r\n\r\n", preview.total_length);
+    client->refbuf->len = strlen (client->refbuf->data);
+    client->respcode = 200;
+
+	return 0;
+}
+
+
+static int get_image (client_t *client, struct ogg_codec_tag *codec)
+{
+    theora_codec_t *theora = codec->specific;
+    return write_video_preview (client, theora->video_preview);
+}
+
+
+/* ok it has to be optimized but for now it's clean, and it's ok ;) */
+static void yuv2rgb (yuv_buffer *_yuv, video_preview_t *video_preview) 
+{
+	int                     i,j;
+	int                     crop_offset;
+
+	int			y_offset;
+	int			ypp_offset;
+	int			uv_offset;
+
+	/* rgba surface pointer */
+	unsigned char          *prgb;
+	yuv_buffer             *yuv;
+
+	unsigned char    	y;
+	unsigned char		ypp;
+	unsigned char		u;
+	unsigned char		v;
+
+	yuv = _yuv;
+	
+	crop_offset = (video_preview -> x_crop_offset) + 
+		(yuv -> y_stride) * (video_preview -> y_crop_offset);
+	prgb = (unsigned char *)video_preview -> rgb_image;
+
+	for (i = 0; i < video_preview -> video_height; i++ ) {
+		for ( j = 0; j < video_preview -> video_width / 2; j++ ) {
+
+			y_offset	= yuv -> y_stride  *  i    + j*2 + crop_offset;
+			ypp_offset	= yuv -> y_stride  *  i    + j*2 + crop_offset + 1;
+			uv_offset	= yuv -> uv_stride * (i/2) + j   + crop_offset;
+
+			y		= *(yuv->y + y_offset);
+			ypp		= *(yuv->y + ypp_offset);
+			u		= *(yuv->u + uv_offset);
+			v		= *(yuv->v + uv_offset);
+
+			/* R G B A */
+			*prgb				=  clip (y + 1.402f * (v-128)) ;
+			prgb++;
+			*prgb				=  clip (y - 0.34414f * (u-128) - 0.71414f * (v-128)) ;
+			prgb++;
+			*prgb				=  clip (y + 1.772 * (u-128)) ;
+			prgb++;
+			*prgb				= 255;
+			prgb++;
+
+			/* R G B A */
+			*prgb				=  clip (ypp + 1.402f * (v-128)) ;
+			prgb++;
+			*prgb				=  clip (ypp - 0.34414f * (u-128) - 0.71414f * (v-128)) ;
+			prgb++;
+			*prgb				=  clip (ypp + 1.772 * (u-128)) ;
+			prgb++;
+			*prgb				= 255;
+			prgb++;
+		}
+	}
+}
+
+static int clip (int x)
+{
+	if (x > 255)
+		return 255;
+	else if (x < 0)
+		return 0;
+	return x;
+}
+#endif
+
+
 static void theora_codec_free (ogg_state_t *ogg_info, ogg_codec_t *codec)
 {
     theora_codec_t *theora = codec->specific;
@@ -52,6 +311,14 @@ static void theora_codec_free (ogg_state_t *ogg_info, ogg_codec_t *codec)
     stats_event (ogg_info->mount, "video_quality", NULL);
     stats_event (ogg_info->mount, "frame_rate", NULL);
     stats_event (ogg_info->mount, "frame_size", NULL);
+#ifdef WITH_VIDEO_PREVIEW
+    stats_event (ogg_info->mount, "video_preview", NULL);
+    if (theora->video_preview)
+    {
+        free_video_preview (theora->video_preview);
+        theora_clear (&theora->td);
+    }
+#endif
     theora_info_clear (&theora->ti);
     theora_comment_clear (&theora->tc);
     ogg_stream_clear (&codec->os);
@@ -113,7 +380,30 @@ static refbuf_t *process_theora_page (ogg_state_t *ogg_info, ogg_codec_t *codec,
             return NULL;
         }
         if (theora_packet_iskeyframe (&packet))
+        {
             has_keyframe = 1;
+#ifdef WITH_VIDEO_PREVIEW
+            if (theora->video_preview) 
+            {
+                if (theora->frame_count == -1) 
+                {
+                    theora_decode_init (&theora->td, &theora->ti);
+                    theora->frame_count = 0;
+                }
+                if (theora->frame_count % 16)
+                    theora->frame_count++;
+                else
+                {
+                    yuv_buffer      yuv;
+
+                    theora_decode_packetin (&theora->td, &packet);
+                    theora_decode_YUVout   (&theora->td, &yuv);
+                    yuv2rgb (&yuv, theora->video_preview);
+                    theora->frame_count = 1;
+                }
+            }
+#endif
+        }
     }
     if (header_page)
     {
@@ -126,8 +416,7 @@ static refbuf_t *process_theora_page (ogg_state_t *ogg_info, ogg_codec_t *codec,
 
     if (granulepos != theora->prev_granulepos || granulepos == 0)
     {
-        if (codec->possible_start)
-            refbuf_release (codec->possible_start);
+        refbuf_release (codec->possible_start);
         refbuf_addref (refbuf);
         codec->possible_start = refbuf;
     }
@@ -182,6 +471,20 @@ ogg_codec_t *initial_theora_page (format_plugin_t *plugin, ogg_page *page)
         codec->filtered = 1;
     codec->name = "Theora";
 
+#ifdef WITH_VIDEO_PREVIEW
+    /* check for video_preview config and video_preview initialization */
+    if (1)
+    {
+        theora_info *ti = &theora_codec->ti;
+        theora_codec->video_preview = init_video_preview (ti->width, ti->height, ti->offset_x, ti->offset_y);
+        theora_codec->frame_count = -1;
+        codec->get_image = get_image;
+        stats_event_args (ogg_info->mount, "video_preview", "/admin/showimage?mount=%s&serial=%ld", ogg_info->mount, codec->os.serialno);
+        //theora_decode_init (&theora_codec->td, &theora_codec->ti);
+        // theora_codec->frame_count = -1;
+    }
+#endif
+
     format_ogg_attach_header (codec, page);
     if (codec->filtered == 0)
         ogg_info->codec_sync = codec;
diff --git a/src/fserve.c b/src/fserve.c
index 62574c0f..f4a42d1d 100644
--- a/src/fserve.c
+++ b/src/fserve.c
@@ -296,8 +296,9 @@ static void *fserv_thread_function(void *arg)
                             client_tree_changed = 1;
                             continue;
                         }
-                        client_set_queue (client, refbuf->next);
-                        refbuf = client->refbuf;
+                        refbuf = refbuf->next;
+                        refbuf_release (client->refbuf);
+                        client->refbuf = refbuf;
                         bytes = refbuf->len;
                     }
                     refbuf->len = bytes;
diff --git a/src/global.c b/src/global.c
index 39053192..5b0bbe97 100644
--- a/src/global.c
+++ b/src/global.c
@@ -45,8 +45,7 @@ void global_initialize(void)
     global.source_tree = avl_tree_new(source_compare_sources, NULL);
     thread_mutex_create("global", &_global_mutex);
     thread_spin_create ("xyz", &global.spinlock);
-    /* do a sampling based on 1/10ths of a second */
-    global.out_bitrate = rate_setup (60);
+    global.out_bitrate = rate_setup (151, 1000);
 }
 
 void global_shutdown(void)
@@ -70,10 +69,8 @@ void global_unlock(void)
 
 void global_add_bitrates (struct rate_calc *rate, unsigned long value)
 {
-    time_t t = (time_t)(global.time_ms/100);
-
     thread_spin_lock (&global.spinlock);
-    rate_add (rate, value, t);
+    rate_add (rate, value, global.time_ms);
     thread_spin_unlock (&global.spinlock);
 }
 
@@ -88,7 +85,7 @@ unsigned long global_getrate_avg (struct rate_calc *rate)
 {
     unsigned long v;
     thread_spin_lock (&global.spinlock);
-    v = rate_avg (rate)*10L;
+    v = rate_avg (rate);
     thread_spin_unlock (&global.spinlock);
     return v;
 }
diff --git a/src/refbuf.c b/src/refbuf.c
index 5e22f3f7..dd6d918d 100644
--- a/src/refbuf.c
+++ b/src/refbuf.c
@@ -35,7 +35,7 @@ void refbuf_shutdown(void)
 {
 }
 
-refbuf_t *refbuf_new(size_t size)
+refbuf_t *refbuf_new (unsigned int size)
 {
     refbuf_t *refbuf;
 
diff --git a/src/refbuf.h b/src/refbuf.h
index f2d72c29..4fd0e31b 100644
--- a/src/refbuf.h
+++ b/src/refbuf.h
@@ -35,7 +35,7 @@ typedef struct _refbuf_tag
 void refbuf_initialize(void);
 void refbuf_shutdown(void);
 
-refbuf_t *refbuf_new(size_t size);
+refbuf_t *refbuf_new(unsigned int size);
 void refbuf_addref(refbuf_t *self);
 void refbuf_release(refbuf_t *self);
 
diff --git a/src/slave.c b/src/slave.c
index 2848bc9f..5e9a54a1 100644
--- a/src/slave.c
+++ b/src/slave.c
@@ -1075,12 +1075,17 @@ static void *_slave_thread(void *arg)
 
             update_from_master (config);
 
+            thread_mutex_lock (&(config_locks()->relay_lock));
             cleanup_relays = update_relays (&global.relays, config->relay);
 
             config_release_config();
         }
+        else
+            thread_mutex_lock (&(config_locks()->relay_lock));
+
         relay_check_streams (global.relays, cleanup_relays, skip_timer);
         relay_check_streams (global.master_relays, NULL, skip_timer);
+        thread_mutex_unlock (&(config_locks()->relay_lock));
 
         if (update_settings)
         {
diff --git a/src/source.c b/src/source.c
index 7a6a2813..9fbf80b3 100644
--- a/src/source.c
+++ b/src/source.c
@@ -254,6 +254,8 @@ void source_clear_source (source_t *source)
     {
         refbuf_t *to_go = p;
         p = to_go->next;
+        if (to_go->_count > 1)
+            WARN1 ("buffer is %d", to_go->_count);
         refbuf_release (to_go);
     }
     /* the source holds 2 references on the very latest so that one
@@ -434,9 +436,9 @@ static void update_source_stats (source_t *source)
     unsigned long kbytes_read = source->bytes_read_since_update/1024;
 
     source->format->sent_bytes += kbytes_sent*1024;
-    stats_event_args (source->mount, "outgoing_bitrate", "%ld", 
+    stats_event_args (source->mount, "outgoing_kbitrate", "%ld",
             (8 * rate_avg (source->format->out_bitrate))/1000);
-    stats_event_args (source->mount, "incoming_bitrate", "%ld", incoming_rate/1000);
+    stats_event_args (source->mount, "incoming_bitrate", "%ld", incoming_rate);
     stats_event_args (source->mount, "total_bytes_read",
             "%"PRIu64, source->format->read_bytes);
     stats_event_args (source->mount, "total_bytes_sent",
@@ -682,7 +684,7 @@ static int send_to_listener (source_t *source, client_t *client, int deletion_ex
 
         total_written += bytes;
     }
-    rate_add (source->format->out_bitrate, total_written, global.time);
+    rate_add (source->format->out_bitrate, total_written, global.time_ms);
     source->bytes_sent_since_update += total_written;
 
     global_add_bitrates (global.out_bitrate, total_written);
@@ -778,7 +780,7 @@ static void process_listeners (source_t *source, int fast_clients_only, int dele
         }
         stats_event_args (source->mount, "listeners", "%lu", source->listeners);
         if (source->listeners == 0)
-            rate_add (source->format->out_bitrate, 0, 0);
+            rate_reduce (source->format->out_bitrate, 0);
         /* change of listener numbers, so reduce scope of global sampling */
         global_reduce_bitrate_sampling (global.out_bitrate);
         /* do we need to shutdown an on-demand relay */
@@ -874,8 +876,8 @@ static void source_init (source_t *source)
 
     thread_mutex_lock (&source->lock);
 
-    source->format->in_bitrate = rate_setup (source->avg_bitrate_duration+1);
-    source->format->out_bitrate = rate_setup (source->avg_bitrate_duration+1);
+    source->format->in_bitrate = rate_setup (source->avg_bitrate_duration+1, 1);
+    source->format->out_bitrate = rate_setup (120, 1000);
     source->running = 1;
 }
 
@@ -1212,10 +1214,17 @@ static void source_apply_mount (source_t *source, mount_proxy *mountinfo)
         source->avg_bitrate_duration = 60;
 
     /* needs a better mechanism, probably via a client_t handle */
+    free (source->dumpfilename);
+    source->dumpfilename = NULL;
     if (mountinfo && mountinfo->dumpfile)
     {
-        free (source->dumpfilename);
-        source->dumpfilename = strdup (mountinfo->dumpfile);
+        time_t now = time(NULL);
+        struct tm local;
+        char buffer[PATH_MAX];
+
+        localtime_r (&now, &local);
+        strftime (buffer, sizeof (buffer), mountinfo->dumpfile, &local);
+        source->dumpfilename = strdup (buffer);
     }
     /* handle changes in intro file setting */
     if (source->intro_file)
@@ -1268,7 +1277,7 @@ void source_update_settings (ice_config_t *config, source_t *source, mount_proxy
     /*  skip if source is a fallback to file */
     if (source->running && source->client == NULL)
     {
-        stats_event_hidden (source->mount, NULL, 1);
+        stats_event_hidden (source->mount, NULL, NULL, STATS_HIDDEN);
         return;
     }
     /* set global settings first */
@@ -1294,13 +1303,6 @@ void source_update_settings (ice_config_t *config, source_t *source, mount_proxy
 
     if (mountinfo)
     {
-        if (mountinfo->hidden)
-        {
-            stats_event_hidden (source->mount, NULL, 1);
-            DEBUG0 ("hidden from public");
-        }
-        else
-            stats_event_hidden (source->mount, NULL, 0);
         if (mountinfo->on_connect)
             DEBUG1 ("connect script \"%s\"", mountinfo->on_connect);
         if (mountinfo->on_disconnect)
@@ -1309,12 +1311,21 @@ void source_update_settings (ice_config_t *config, source_t *source, mount_proxy
             DEBUG1 ("fallback_when_full to %u", mountinfo->fallback_when_full);
         DEBUG1 ("max listeners to %d", mountinfo->max_listeners);
         stats_event_args (source->mount, "max_listeners", "%d", mountinfo->max_listeners);
+        stats_event_hidden (source->mount, "cluster_password", mountinfo->cluster_password, STATS_SLAVE);
+        if (mountinfo->hidden)
+        {
+            stats_event_hidden (source->mount, NULL, NULL, STATS_HIDDEN);
+            DEBUG0 ("hidden from public");
+        }
+        else
+            stats_event_hidden (source->mount, NULL, NULL, STATS_PUBLIC);
     }
     else
     {
         DEBUG0 ("max listeners is not specified");
         stats_event (source->mount, "max_listeners", "unlimited");
-        stats_event_hidden (source->mount, NULL, 0);
+        stats_event_hidden (source->mount, "cluster_password", NULL, STATS_SLAVE);
+        stats_event_hidden (source->mount, NULL, NULL, STATS_PUBLIC);
     }
     DEBUG1 ("public set to %d", source->yp_public);
     DEBUG1 ("queue size to %u", source->queue_size_limit);
@@ -1459,7 +1470,7 @@ static void *source_fallback_file (void *arg)
         httpp_setvar (parser, "content-type", type);
         free (type);
 
-        stats_event_hidden (source->mount, NULL, 1);
+        stats_event_hidden (source->mount, NULL, NULL, STATS_HIDDEN);
         source->yp_public = 0;
         source->intro_file = file;
         source->parser = parser;
@@ -1522,7 +1533,7 @@ void source_recheck_mounts (int update_all)
             }
             else if (update_all)
             {
-                stats_event_hidden (mount->mountname, NULL, mount->hidden);
+                stats_event_hidden (mount->mountname, NULL, NULL, mount->hidden?STATS_HIDDEN:0);
                 stats_event_args (mount->mountname, "listenurl", "http://%s:%d%s",
                         config->hostname, config->port, mount->mountname);
                 stats_event (mount->mountname, "listeners", "0");
diff --git a/src/stats.c b/src/stats.c
index dcd4a8e0..28065abc 100644
--- a/src/stats.c
+++ b/src/stats.c
@@ -53,7 +53,7 @@
 #define STATS_EVENT_ADD     3
 #define STATS_EVENT_SUB     4
 #define STATS_EVENT_REMOVE  5
-#define STATS_EVENT_HIDDEN  6
+#define STATS_EVENT_HIDDEN  0x80
 
 typedef struct _stats_node_tag
 {
@@ -80,43 +80,47 @@ typedef struct _stats_source_tag
     avl_tree *stats_tree;
 } stats_source_t;
 
-typedef struct _event_queue_tag
-{
-    volatile stats_event_t *head;
-    volatile stats_event_t **tail;
-} event_queue_t;
-
-#define event_queue_init(qp)    { (qp)->head = NULL; (qp)->tail = &(qp)->head; }
-
 typedef struct _event_listener_tag
 {
-    event_queue_t queue;
-    mutex_t mutex;
+    client_t *client;
     int master;
+    int hidden_level;
     char *source;
 
+    /* queue for unwritten stats to stats clients */
+    refbuf_t *queue, **queue_recent_p;
+    unsigned int content_len;
+
     struct _event_listener_tag *next;
 } event_listener_t;
 
+
+typedef struct _stats_tag
+{
+    avl_tree *global_tree;
+    avl_tree *source_tree;
+
+    /* list of listeners for stats */
+    event_listener_t *event_listeners, *listeners_removed;
+
+} stats_t;
+
 static volatile int _stats_running = 0;
-static volatile int _stats_threads = 0;
 
 static stats_t _stats;
 static mutex_t _stats_mutex;
 
-static volatile event_listener_t *_event_listeners;
-
 
 static int _compare_stats(void *a, void *b, void *arg);
 static int _compare_source_stats(void *a, void *b, void *arg);
 static int _free_stats(void *key);
 static int _free_source_stats(void *key);
-static void _add_event_to_queue(stats_event_t *event, event_queue_t *queue);
 static stats_node_t *_find_node(avl_tree *tree, const char *name);
 static stats_source_t *_find_source(avl_tree *tree, const char *source);
-static void _free_event(stats_event_t *event);
-static stats_event_t *_get_event_from_queue (event_queue_t *queue);
 static void process_event (stats_event_t *event);
+static void _add_stats_to_stats_client (event_listener_t *listener, const char *fmt, va_list ap);
+static void stats_listener_send (int flags, const char *fmt, ...);
+static void process_event_unlocked (stats_event_t *event);
 
 
 /* simple helper function for creating an event */
@@ -125,8 +129,10 @@ static void build_event (stats_event_t *event, const char *source, const char *n
     event->source = (char *)source;
     event->name = (char *)name;
     event->value = (char *)value;
+    event->hidden = STATS_PUBLIC;
+    if (source) event->hidden |= STATS_SLAVE;
     if (value)
-        event->action = 0;
+        event->action = STATS_EVENT_SET;
     else
         event->action = STATS_EVENT_REMOVE;
 }
@@ -134,75 +140,52 @@ static void build_event (stats_event_t *event, const char *source, const char *n
 
 void stats_initialize(void)
 {
-    _event_listeners = NULL;
+    if (_stats_running)
+        return;
 
     /* set up global struct */
     _stats.global_tree = avl_tree_new(_compare_stats, NULL);
     _stats.source_tree = avl_tree_new(_compare_source_stats, NULL);
 
+    _stats.event_listeners = NULL;
+    _stats.listeners_removed = NULL;
+
     /* set up global mutex */
     thread_mutex_create("stats", &_stats_mutex);
 
-    /* fire off the stats thread */
     _stats_running = 1;
 
     stats_event_time (NULL, "server_start");
 
     /* global currently active stats */
-    stats_event (NULL, "clients", "0");
-    stats_event (NULL, "connections", "0");
-    stats_event (NULL, "sources", "0");
-    stats_event (NULL, "stats", "0");
+    stats_event_hidden (NULL, "clients", "0", STATS_COUNTERS);
+    stats_event_hidden (NULL, "connections", "0", STATS_COUNTERS);
+    stats_event_hidden (NULL, "sources", "0", STATS_COUNTERS);
+    stats_event_hidden (NULL, "stats", "0", STATS_COUNTERS);
     stats_event (NULL, "listeners", "0");
 
     /* global accumulating stats */
-    stats_event (NULL, "client_connections", "0");
-    stats_event (NULL, "source_client_connections", "0");
-    stats_event (NULL, "source_relay_connections", "0");
-    stats_event (NULL, "source_total_connections", "0");
-    stats_event (NULL, "stats_connections", "0");
-    stats_event (NULL, "listener_connections", "0");
-    stats_event (NULL, "outgoing_kbitrate", "0");
+    stats_event_hidden (NULL, "client_connections", "0", STATS_COUNTERS);
+    stats_event_hidden (NULL, "source_client_connections", "0", STATS_COUNTERS);
+    stats_event_hidden (NULL, "source_relay_connections", "0", STATS_COUNTERS);
+    stats_event_hidden (NULL, "source_total_connections", "0", STATS_COUNTERS);
+    stats_event_hidden (NULL, "stats_connections", "0", STATS_COUNTERS);
+    stats_event_hidden (NULL, "listener_connections", "0", STATS_COUNTERS);
+    stats_event_hidden (NULL, "outgoing_kbitrate", "0", STATS_COUNTERS);
 }
 
 void stats_shutdown(void)
 {
-    int n;
-
     if(!_stats_running) /* We can't shutdown if we're not running. */
         return;
 
-    /* wait for thread to exit */
     _stats_running = 0;
 
-    /* wait for other threads to shut down */
-    do {
-        thread_sleep(300000);
-        thread_mutex_lock(&_stats_mutex);
-        n = _stats_threads;
-        thread_mutex_unlock(&_stats_mutex);
-    } while (n > 0);
-    INFO0("stats thread finished");
-
-    /* free the queues */
-
     thread_mutex_destroy(&_stats_mutex);
     avl_tree_free(_stats.source_tree, _free_source_stats);
     avl_tree_free(_stats.global_tree, _free_stats);
 }
 
-stats_t *stats_get_stats(void)
-{
-    /* lock global stats
-    
-     copy stats
-
-     unlock global stats
-
-     return copied stats */
-
-    return NULL;
-}
 
 /* simple name=tag stat create/update */
 void stats_event(const char *source, const char *name, const char *value)
@@ -259,29 +242,27 @@ void stats_event_conv(const char *mount, const char *name, const char *value, co
 
 /* make stat hidden (non-zero). name can be NULL if it applies to a whole
  * source stats tree. */
-void stats_event_hidden (const char *source, const char *name, int hidden)
+void stats_event_hidden (const char *source, const char *name, const char *value, int hidden)
 {
-    const char *str = NULL;
     stats_event_t event;
 
-    if (hidden)
-        str = "";
-    build_event (&event, source, name, NULL);
-    event.action = STATS_EVENT_HIDDEN;
+    build_event (&event, source, name, value);
+    event.hidden = hidden;
+    event.action |= STATS_EVENT_HIDDEN;
     process_event (&event);
 }
 
 /* printf style formatting for stat create/update */
 void stats_event_args(const char *source, char *name, char *format, ...)
 {
-    char buf[1024];
     va_list val;
     int ret;
+    char buf[1024];
 
     if (name == NULL)
         return;
     va_start(val, format);
-    ret = vsnprintf(buf, 1024, format, val);
+    ret = vsnprintf(buf, sizeof (buf), format, val);
     va_end(val);
 
     if (ret < 0 || (unsigned int)ret >= sizeof (buf))
@@ -420,38 +401,16 @@ static stats_source_t *_find_source(avl_tree *source_tree, const char *source)
     return NULL;
 }
 
-static stats_event_t *_copy_event(stats_event_t *event)
-{
-    stats_event_t *copy = (stats_event_t *)calloc(1, sizeof(stats_event_t));
-    if (event->source) 
-        copy->source = (char *)strdup(event->source);
-    else
-        copy->source = NULL;
-    if (event->name)
-        copy->name = (char *)strdup(event->name);
-    if (event->value)
-        copy->value = (char *)strdup(event->value);
-    else
-        copy->value = NULL;
-    copy->hidden = event->hidden;
-    copy->next = NULL;
-
-    return copy;
-}
-
 
 /* helper to apply specialised changes to a stats node */
 static void modify_node_event (stats_node_t *node, stats_event_t *event)
 {
     if (node == NULL || event == NULL)
         return;
-    if (event->action == STATS_EVENT_HIDDEN)
+    if (event->action & STATS_EVENT_HIDDEN)
     {
-        if (event->value)
-            node->hidden = 1;
-        else
-            node->hidden = 0;
-        return;
+        node->hidden = event->hidden;
+        event->action &= ~STATS_EVENT_HIDDEN;
     }
     if (event->action != STATS_EVENT_SET)
     {
@@ -483,7 +442,7 @@ static void modify_node_event (stats_node_t *node, stats_event_t *event)
 
 static void process_global_event (stats_event_t *event)
 {
-    stats_node_t *node;
+    stats_node_t *node = NULL;
 
     /* DEBUG3("global event %s %s %d", event->name, event->value, event->action); */
     if (event->action == STATS_EVENT_REMOVE)
@@ -491,16 +450,18 @@ static void process_global_event (stats_event_t *event)
         /* we're deleting */
         node = _find_node(_stats.global_tree, event->name);
         if (node != NULL)
+        {
+            stats_listener_send (node->hidden, "DELETE global %s\n", event->name);
             avl_delete(_stats.global_tree, (void *)node, _free_stats);
+        }
         return;
     }
     node = _find_node(_stats.global_tree, event->name);
     if (node)
     {
         modify_node_event (node, event);
-        DEBUG3 ("update node on %s \"%s\" (%s)",
-                event->source ? event->source : "global",
-                node->name, node->value);
+        stats_listener_send (node->hidden, "EVENT global %s %s\n", node->name, node->value);
+        DEBUG2 ("update node on global \"%s\" (%s)", node->name, node->value);
     }
     else
     {
@@ -508,8 +469,10 @@ static void process_global_event (stats_event_t *event)
         node = (stats_node_t *)calloc(1, sizeof(stats_node_t));
         node->name = (char *)strdup(event->name);
         node->value = (char *)strdup(event->value);
+        node->hidden = event->hidden;
 
         avl_insert(_stats.global_tree, (void *)node);
+        stats_listener_send (node->hidden, "EVENT global %s %s\n", event->name, event->value);
     }
 }
 
@@ -517,6 +480,8 @@ static void process_global_event (stats_event_t *event)
 static void process_source_event (stats_event_t *event)
 {
     stats_source_t *snode = _find_source(_stats.source_tree, event->source);
+    stats_node_t *node = NULL;
+
     if (snode == NULL)
     {
         if (event->action == STATS_EVENT_REMOVE)
@@ -527,16 +492,13 @@ static void process_source_event (stats_event_t *event)
         DEBUG1 ("new source stat %s", event->source);
         snode->source = (char *)strdup(event->source);
         snode->stats_tree = avl_tree_new(_compare_stats, NULL);
-        if (event->action == STATS_EVENT_HIDDEN)
-            snode->hidden = 1;
-        else
-            snode->hidden = 0;
+        snode->hidden = STATS_SLAVE|STATS_GENERAL|STATS_HIDDEN;
 
         avl_insert(_stats.source_tree, (void *)snode);
     }
     if (event->name)
     {
-        stats_node_t *node = _find_node(snode->stats_tree, event->name);
+        node = _find_node (snode->stats_tree, event->name);
         if (node == NULL)
         {
             if (event->action == STATS_EVENT_REMOVE)
@@ -548,8 +510,11 @@ static void process_source_event (stats_event_t *event)
                 node = (stats_node_t *)calloc(1,sizeof(stats_node_t));
                 node->name = (char *)strdup(event->name);
                 node->value = (char *)strdup(event->value);
-                node->hidden = snode->hidden;
+                node->hidden = event->hidden;
+                if (snode->hidden | STATS_HIDDEN)
+                    node->hidden |= STATS_HIDDEN;
 
+                stats_listener_send (node->hidden, "EVENT %s %s %s\n", event->source, event->name, event->value);
                 avl_insert(snode->stats_tree, (void *)node);
             }
             return;
@@ -557,24 +522,42 @@ static void process_source_event (stats_event_t *event)
         if (event->action == STATS_EVENT_REMOVE)
         {
             DEBUG1 ("delete node %s", event->name);
+            stats_listener_send (node->hidden, "DELETE %s %s\n", event->source, event->name);
             avl_delete(snode->stats_tree, (void *)node, _free_stats);
             return;
         }
         modify_node_event (node, event);
         return;
     }
-    if (event->action == STATS_EVENT_HIDDEN)
+    /* change source hidden status */
+    if (event->action & STATS_EVENT_HIDDEN)
     {
         avl_node *node = avl_get_first (snode->stats_tree);
+        int visible = 0;
 
-        if (event->value)
-            snode->hidden = 1;
+        if ((event->hidden&STATS_HIDDEN) == (snode->hidden&STATS_HIDDEN))
+            return;
+        if (snode->hidden & STATS_HIDDEN)
+        {
+            snode->hidden &= ~STATS_HIDDEN;
+            stats_listener_send (snode->hidden, "NEW %s\n", snode->source);
+            visible = 1;
+        }
         else
-            snode->hidden = 0;
+        {
+            stats_listener_send (snode->hidden, "DELETE %s\n", snode->source);
+            snode->hidden |= STATS_HIDDEN;
+        }
         while (node)
         {
             stats_node_t *stats = (stats_node_t*)node->key;
-            stats->hidden = snode->hidden;
+            if (visible)
+            {
+                stats->hidden &= ~STATS_HIDDEN;
+                stats_listener_send (stats->hidden, "EVENT %s %s %s\n", snode->source, stats->name, stats->value);
+            }
+            else
+                stats->hidden |= STATS_HIDDEN;
             node = avl_get_next (node);
         }
         return;
@@ -582,6 +565,7 @@ static void process_source_event (stats_event_t *event)
     if (event->action == STATS_EVENT_REMOVE)
     {
         DEBUG1 ("delete source node %s", event->source);
+        stats_listener_send (snode->hidden, "DELETE %s\n", event->source);
         avl_delete(_stats.source_tree, (void *)snode, _free_source_stats);
     }
 }
@@ -595,39 +579,98 @@ void stats_event_time (const char *mount, const char *name)
 
     localtime_r (&now, &local);
     strftime (buffer, sizeof (buffer), ICECAST_TIME_FMT, &local);
-    stats_event (mount, name, buffer);
+    stats_event_hidden (mount, name, buffer, STATS_GENERAL);
 }
 
-void stats_listener_send (stats_event_t *event)
+
+static void stats_listeners_send (event_listener_t *listener)
 {
-    event_listener_t *listener = (event_listener_t *)_event_listeners;
+    int loop = 6;
+    int ret;
+    client_t *client = listener->client;
+
+    while (loop)
+    {
+        if (format_advance_queue (NULL, client) < 0)
+            break;
+        ret = format_generic_write_to_client (client);
+        if (ret < 0)
+            break;
+        listener->content_len -= ret;
+        loop--;
+    }
+}
+
+static void clear_stats_queue (event_listener_t *listener)
+{
+    while (listener->queue && listener->queue->_count == 1)
+    {
+        refbuf_t *to_go = listener->queue;
+        listener->queue = to_go->next;
+        refbuf_release (to_go);
+    }
+}
+
+
+static void stats_listener_send (int hidden_level, const char *fmt, ...)
+{
+    va_list ap;
+    event_listener_t *listener = _stats.event_listeners,
+                     **trail = &_stats.event_listeners;
+
+    va_start(ap, fmt);
 
     while (listener)
     {
-        int send_it = 1;
+        client_t *client = listener->client;
 
-        if (event->source && listener->source &&
-                strcmp (event->source, listener->source) != 0)
-            send_it = 0;
-
-        if (send_it)
+        if (listener->hidden_level & hidden_level)
         {
-            stats_event_t *copy = _copy_event(event);
-            thread_mutex_lock (&listener->mutex);
-            _add_event_to_queue (copy, &listener->queue);
-            thread_mutex_unlock (&listener->mutex);
+            _add_stats_to_stats_client (listener, fmt, ap);
         }
+        stats_listeners_send (listener);
+        if (client->con->error || listener->content_len > 10000)
+        {
+            stats_event_t stats_count;
+            char buffer [20];
 
+            *trail = listener->next;
+
+            build_event (&stats_count, NULL, "stats_connections", buffer);
+            stats_count.action = STATS_EVENT_DEC;
+            process_event_unlocked (&stats_count);
+
+            /* moved this listener so that final cleanup can be done outside of lock */
+            listener->next = _stats.listeners_removed;
+            _stats.listeners_removed = listener;
+
+            listener = *trail;
+            continue;
+        }
+        /* reduce queue if unsued */
+        clear_stats_queue (listener);
+
+        trail = &listener->next;
         listener = listener->next;
     }
+    va_end(ap);
 }
 
 void stats_global (ice_config_t *config)
 {
-    stats_event (NULL, "server_id", config->server_id);
-    stats_event (NULL, "host", config->hostname);
+    stats_event_hidden (NULL, "server_id", config->server_id, STATS_GENERAL);
+    stats_event_hidden (NULL, "host", config->hostname, STATS_GENERAL);
     stats_event (NULL, "location", config->location);
     stats_event (NULL, "admin", config->admin);
+#if 0
+    /* restart a master stats connection */
+    config->master = calloc (1, sizeof ice_master_details);
+    config->master->hostname = xmlCharStrdup ("127.0.0.1");
+    config->master->port = 8000;
+    config->master->username = xmlCharStrdup ("relay");
+    config->master->password = xmlCharStrdup ("relayme");
+    _stats.sock = sock_connect_wto_bind (server, port, bind, 10);
+#endif
 }
 
 static void process_event_unlocked (stats_event_t *event)
@@ -637,10 +680,6 @@ static void process_event_unlocked (stats_event_t *event)
         process_global_event (event);
     else
         process_source_event (event);
-
-    /* now we have an event that's been processed into the running stats */
-    /* this event should get copied to event listeners' queues */
-    stats_listener_send (event);
 }
 
 static void process_event (stats_event_t *event)
@@ -652,300 +691,230 @@ static void process_event (stats_event_t *event)
     thread_mutex_unlock (&_stats_mutex);
 }
 
-/* you must have the _stats_mutex locked here */
-static void _unregister_listener(event_listener_t *listener)
+
+static int _append_to_bufferv (refbuf_t *refbuf, int max_len, const char *fmt, va_list ap)
 {
-    event_listener_t **prev = (event_listener_t **)&_event_listeners,
-                     *current = *prev;
-    stats_event_t stats_count, *event;
-    char buffer [VAL_BUFSIZE];
+    char *buf = (char*)refbuf->data + refbuf->len;
+    int len = max_len - refbuf->len;
+    int ret;
+    va_list vl;
 
-    while (current)
-    {
-        if (current == listener)
-        {
-            *prev = current->next;
-            break;
-        }
-        prev = ¤t->next;
-        current = *prev;
-    }
-
-    /* remove this listener before sending this change */
-    build_event (&stats_count, NULL, "stats_connections", buffer);
-    stats_count.action = STATS_EVENT_DEC;
-    process_event_unlocked (&stats_count);
-
-    /* flush any extra that sneaked on at the last moment */
-    while ((event = _get_event_from_queue (&listener->queue)) != NULL)
-        _free_event (event);
-}
-
-
-static stats_event_t *_make_event_from_node(stats_node_t *node, char *source)
-{
-    stats_event_t *event = (stats_event_t *)malloc(sizeof(stats_event_t));
-    
-    if (source != NULL)
-        event->source = (char *)strdup(source);
-    else
-        event->source = NULL;
-    event->name = (char *)strdup(node->name);
-    event->value = (char *)strdup(node->value);
-    event->hidden = node->hidden;
-    event->action = STATS_EVENT_SET;
-    event->next = NULL;
-
-    return event;
-}
-
-
-static void _add_event_to_queue(stats_event_t *event, event_queue_t *queue)
-{
-    *queue->tail = event;
-    queue->tail = (volatile stats_event_t **)&event->next;
-}
-
-
-static stats_event_t *_get_event_from_queue (event_queue_t *queue)
-{
-    stats_event_t *event = NULL;
-
-    if (queue && queue->head)
-    {
-        event = (stats_event_t *)queue->head;
-        queue->head = event->next;
-        if (queue->head == NULL)
-            queue->tail = &queue->head;
-    }
-
-    return event;
-}
-
-static int _send_event_to_client(stats_event_t *event, client_t *client)
-{
-    int len;
-    char buf [200];
-
-    /* send data to the client!!!! */
-    len = snprintf (buf, sizeof (buf), "EVENT %s %s %s\n",
-            (event->source != NULL) ? event->source : "global",
-            event->name ? event->name : "null",
-            event->value ? event->value : "null");
-    if (len > 0 && len < (int)sizeof (buf))
-    {
-        client_send_bytes (client, buf, len);
-        if (client->con->error)
-            return -1;
-    }
+    va_copy (vl, ap);
+    ret = vsnprintf (buf, len, fmt, vl);
+    if (ret < 0 || ret >= len)
+        return -1;
+    refbuf->len += ret;
     return 0;
 }
 
-void _dump_stats_to_queue (event_queue_t *queue)
+static int _append_to_buffer (refbuf_t *refbuf, int max_len, const char *fmt, ...)
 {
-    avl_node *node;
-    avl_node *node2;
-    stats_event_t *event;
-    stats_source_t *source;
+    int ret;
+    va_list va;
+
+    va_start (va, fmt);
+    ret = _append_to_bufferv (refbuf, max_len, fmt, va);
+    va_end(va);
+    return ret;
+}
+
+
+static void _add_node_to_stats_client (event_listener_t *listener, refbuf_t *refbuf)
+{
+    *listener->queue_recent_p = refbuf;
+    listener->queue_recent_p = &refbuf->next;
+    listener->content_len += refbuf->len;
+}
+
+
+static void _add_stats_to_stats_client (event_listener_t *listener,const char *fmt, va_list ap)
+{
+    unsigned int size = 50;
+    while (size < 300)
+    {
+        refbuf_t *refbuf = refbuf_new (size);
+        refbuf->len = 0;
+
+        if (_append_to_bufferv (refbuf, size, fmt, ap) == 0)
+        {
+            _add_node_to_stats_client (listener, refbuf);
+            return;
+        }
+        refbuf_release (refbuf);
+        size += 100;
+    }
+}
+
+
+static xmlNodePtr _dump_stats_to_doc (xmlNodePtr root, const char *show_mount, int hidden)
+{
+    avl_node *avlnode;
+    xmlNodePtr ret = NULL;
 
     thread_mutex_lock(&_stats_mutex);
-    /* first we fill our queue with the current stats */
-    /* start with the global stats */
-    node = avl_get_first(_stats.global_tree);
-    while (node) {
-        event = _make_event_from_node((stats_node_t *)node->key, NULL);
-        _add_event_to_queue(event, queue);
-
-        node = avl_get_next(node);
+    /* general stats first */
+    avlnode = avl_get_first(_stats.global_tree);
+    while (avlnode)
+    {
+        stats_node_t *stat = avlnode->key;
+        if (stat->hidden & hidden)
+            xmlNewTextChild (root, NULL, XMLSTR(stat->name), XMLSTR(stat->value));
+        avlnode = avl_get_next (avlnode);
     }
+    /* now per mount stats */
+    avlnode = avl_get_first(_stats.source_tree);
+    while (avlnode)
+    {
+        stats_source_t *source = (stats_source_t *)avlnode->key;
+        if (source->hidden & hidden &&
+                (show_mount == NULL || strcmp (show_mount, source->source) == 0))
+        {
+            avl_node *avlnode2 = avl_get_first (source->stats_tree);
+            xmlNodePtr xmlnode = xmlNewTextChild (root, NULL, XMLSTR("source"), NULL);
 
-    /* now the stats for each source */
-    node = avl_get_first(_stats.source_tree);
-    while (node) {
-        source = (stats_source_t *)node->key;
-        node2 = avl_get_first(source->stats_tree);
-        while (node2) {
-            event = _make_event_from_node((stats_node_t *)node2->key, source->source);
-            _add_event_to_queue(event, queue);
-
-            node2 = avl_get_next(node2);
+            xmlSetProp (xmlnode, XMLSTR("mount"), XMLSTR(source->source));
+            if (ret == NULL)
+                ret = xmlnode;
+            while (avlnode2)
+            {
+                stats_node_t *stat = avlnode2->key;
+                xmlNewTextChild (xmlnode, NULL, XMLSTR(stat->name), XMLSTR(stat->value));
+                avlnode2 = avl_get_next (avlnode2);
+            }
         }
-        
-        node = avl_get_next(node);
+        avlnode = avl_get_next (avlnode);
     }
     thread_mutex_unlock(&_stats_mutex);
+    return ret;
 }
 
+
 /* factoring out code for stats loops
 ** this function copies all stats to queue, and registers 
-** the queue for all new events atomically.
-** note: mutex must already be created!
 */
 static void _register_listener (event_listener_t *listener)
 {
     avl_node *node;
-    avl_node *node2;
-    stats_event_t *event;
-    stats_source_t *source;
     stats_event_t stats_count;
+    refbuf_t *refbuf;
+    size_t size = 8192;
     char buffer[20];
 
     build_event (&stats_count, NULL, "stats_connections", buffer);
     stats_count.action = STATS_EVENT_INC;
     process_event_unlocked (&stats_count);
 
-    /* first we fill our queue with the current stats */
-    
-    /* start with the global stats */
-    node = avl_get_first(_stats.global_tree);
-    while (node) {
-        event = _make_event_from_node((stats_node_t *)node->key, NULL);
-        _add_event_to_queue (event, &listener->queue);
-
-        node = avl_get_next(node);
-    }
-
-    /* now the stats for each source */
-    node = avl_get_first(_stats.source_tree);
-    while (node) {
-        source = (stats_source_t *)node->key;
-        node2 = avl_get_first(source->stats_tree);
-        while (node2) {
-            event = _make_event_from_node((stats_node_t *)node2->key, source->source);
-            _add_event_to_queue (event, &listener->queue);
-
-            node2 = avl_get_next(node2);
-        }
-        
-        node = avl_get_next(node);
-    }
-
-    /* now we register to receive future event notices */
-    listener->next = (event_listener_t *)_event_listeners;
-    _event_listeners = listener;
-}
-
-static void check_uri (event_listener_t *listener, client_t *client)
-{
-    const char *mount = httpp_getvar (client->parser, HTTPP_VAR_URI);
-    if (strcmp (mount, "/") != 0)
-        listener->source = strdup (mount);
-}
-
-
-void *stats_connection(void *arg)
-{
-    client_t *client = (client_t *)arg;
-    stats_event_t *event;
-    event_listener_t listener;
-
-    INFO0 ("stats client starting");
-
-    memset (&listener, 0, sizeof (listener));
-    event_queue_init (&listener.queue);
-    check_uri (&listener, client);
-
-    /* increment the thread count */
-    thread_mutex_lock(&_stats_mutex);
-    _stats_threads++;
-    thread_mutex_create("stats local event", &listener.mutex);
-
-    _register_listener (&listener);
-    thread_mutex_unlock(&_stats_mutex);
-
-    while (_stats_running) {
-        thread_mutex_lock (&listener.mutex);
-        event = _get_event_from_queue (&listener.queue);
-        thread_mutex_unlock (&listener.mutex);
-        if (event != NULL) {
-            if (_send_event_to_client(event, client) < 0) {
-                _free_event(event);
-                break;
-            }
-            _free_event(event);
-            continue;
-        }
-        thread_sleep (500000);
-    }
-
-    thread_mutex_lock(&_stats_mutex);
-    _unregister_listener (&listener);
-    _stats_threads--;
-    thread_mutex_unlock(&_stats_mutex);
-
-    thread_mutex_destroy (&listener.mutex);
-    free (listener.source);
-    client_destroy (client);
-    INFO0 ("stats client finished");
-
-    return NULL;
-}
-
-
-void stats_callback (client_t *client, void *notused)
-{
-    if (client->con->error)
+    while (size < 50000)    /* use a large limit */
     {
-        client_destroy (client);
-        return;
-    }
-    client_set_queue (client, NULL);
-    thread_create("Stats Connection", stats_connection, (void *)client, THREAD_DETACHED);
-}
+        /* first we fill our queue with the current stats */
+        refbuf = refbuf_new (size);
+        refbuf->len = 0;
 
-
-typedef struct _source_xml_tag {
-    char *mount;
-    xmlNodePtr node;
-
-    struct _source_xml_tag *next;
-} source_xml_t;
-
-static xmlNodePtr _find_xml_node(const char *mount, source_xml_t **list, xmlNodePtr root)
-{
-    source_xml_t *node, *node2;
-    int found = 0;
-
-    /* search for existing node */
-    node = *list;
-    while (node) {
-        if (strcmp(node->mount, mount) == 0) {
-            found = 1;
+        /* starts with the http response header */
+        if (_append_to_buffer (refbuf, size, "HTTP/1.0 200 OK\r\ncapability: streamlist\r\n\r\n") < 0)
+        {
+            refbuf_release (refbuf);
             break;
         }
-        node = node->next;
+        /* now the global stats */
+        node = avl_get_first(_stats.global_tree);
+        while (node)
+        {
+            stats_node_t *stat = node->key;
+
+            node = avl_get_next(node);
+            if ((stat->hidden & listener->hidden_level) == 0)
+                continue;
+            if (_append_to_buffer (refbuf, size, "EVENT global %s %s\n", stat->name, stat->value) < 0)
+            {
+                size += 8192;
+                refbuf_release (refbuf);
+                break;
+            }
+        }
+        if (node) continue; /* catch buffer full case */
+
+        /* now the stats for each source */
+        node = avl_get_first(_stats.source_tree);
+        while (node)
+        {
+            avl_node *node2;
+            stats_source_t *snode = (stats_source_t *)node->key;
+            node = avl_get_next(node);
+            if ((snode->hidden & listener->hidden_level) == 0)
+                continue;
+            if (_append_to_buffer (refbuf, size, "NEW %s\n", snode->source) < 0)
+            {
+                size += 8192;
+                refbuf_release (refbuf);
+                break;
+            }
+            node2 = avl_get_first(snode->stats_tree);
+            while (node2)
+            {
+                stats_node_t *stat = node2->key;
+                node2 = avl_get_next(node2);
+
+                if ((stat->hidden & listener->hidden_level) == 0)
+                    continue;
+                if (_append_to_buffer (refbuf, size, "EVENT %s %s %s\n", snode->source, stat->name, stat->value) < 0)
+                {
+                    size += 8192;
+                    refbuf_release (refbuf);
+                    break;
+                }
+            }
+        }
+        if (node) continue; /* catch buffer full case */
+        break;
     }
 
-    if (found) return node->node;
+    client_set_queue (listener->client, refbuf);
+    _add_node_to_stats_client (listener, refbuf);
 
-    /* if we didn't find it, we must build it and add it to the list */
-
-    /* build node */
-    node = (source_xml_t *)malloc(sizeof(source_xml_t));
-    node->mount = strdup(mount);
-    node->node = xmlNewChild(root, NULL, XMLSTR("source"), NULL);
-    xmlSetProp(node->node, XMLSTR("mount"), XMLSTR(mount));
-    node->next = NULL;
-
-    /* add node */
-    if (*list == NULL) {
-        *list = node;
-    } else {
-        node2 = *list;
-        while (node2->next) node2 = node2->next;
-        node2->next = node;
-    }
-
-    return node->node;
+    /* now we register to receive future event notices */
+    listener->next = _stats.event_listeners;
+    _stats.event_listeners = listener;
 }
 
+static void check_uri (event_listener_t *listener, const char *mount)
+{
+    listener->hidden_level = STATS_PUBLIC;
+    if (strncmp (mount, "/admin/", 7) == 0)
+    {
+        if (strcmp (mount+7, "streams") == 0)
+            listener->hidden_level = STATS_SLAVE|STATS_GENERAL;
+    }
+    /* else
+     * check for reserved mountpoint, show hidden source stats */
+}
+
+
+
+void stats_callback (client_t *client, void *arg)
+{
+    event_listener_t * listener;
+    const char *mount = arg;
+
+    client_set_queue (client, NULL);
+    listener = calloc (1, sizeof (event_listener_t));
+    listener->client = client;
+    listener->queue_recent_p = &listener->queue;
+    check_uri (listener, mount);
+
+    thread_mutex_lock(&_stats_mutex);
+    _register_listener (listener);
+    thread_mutex_unlock(&_stats_mutex);
+}
+
+
 void stats_transform_xslt(client_t *client, const char *uri)
 {
     xmlDocPtr doc;
     char *xslpath = util_get_path_from_normalised_uri (uri);
     const char *mount = httpp_get_query_param (client->parser, "mount");
 
-    stats_get_xml(&doc, 0, mount);
+    stats_get_xml(&doc, STATS_PUBLIC, mount);
 
     xslt_transform(doc, xslpath, client);
 
@@ -953,71 +922,28 @@ void stats_transform_xslt(client_t *client, const char *uri)
     free (xslpath);
 }
 
-void stats_get_xml(xmlDocPtr *doc, int show_hidden, const char *show_mount)
+void stats_get_xml(xmlDocPtr *doc, int hidden, const char *show_mount)
 {
-    stats_event_t *event;
-    event_queue_t queue;
-    xmlNodePtr node, srcnode;
-    source_xml_t *src_nodes = NULL;
-    source_xml_t *next;
-
-    event_queue_init (&queue);
-    _dump_stats_to_queue (&queue);
+    xmlNodePtr node;
 
     *doc = xmlNewDoc (XMLSTR("1.0"));
     node = xmlNewDocNode (*doc, NULL, XMLSTR("icestats"), NULL);
     xmlDocSetRootElement(*doc, node);
 
-    event = _get_event_from_queue(&queue);
-    while (event)
-    {
-        if (event->hidden <= show_hidden)
-        {
-            do
-            {
-                xmlChar *name, *value;
-                if (event->source)
-                {
-                    if (show_mount && strcmp (event->source, show_mount) != 0)
-                        break;
-                    srcnode = _find_xml_node(event->source, &src_nodes, node);
-                }
-                else
-                    srcnode = node;
+    node = _dump_stats_to_doc (node, show_mount, hidden);
 
-                name = xmlEncodeEntitiesReentrant (*doc, XMLSTR(event->name));
-                value = xmlEncodeEntitiesReentrant (*doc, XMLSTR(event->value));
-                xmlNewChild(srcnode, NULL, name, value);
-                xmlFree (value);
-                xmlFree (name);
-            } while (0);
-        }
-
-        _free_event(event);
-        event = _get_event_from_queue(&queue);
-    }
-    if (show_mount)
-    {
+    if (show_mount && node)
+	{
+		source_t *source;
         /* show each listener */
-        source_xml_t *src = src_nodes;
         avl_tree_rlock (global.source_tree);
-        while (src)
-        {
-            source_t *source = source_find_mount_raw (src->mount);
+        source = source_find_mount_raw (show_mount);
 
-            if (source)
-                admin_source_listeners (source, src->node);
+        if (source)
+            admin_source_listeners (source, node);
 
-            src = src->next;
-        }
         avl_tree_unlock (global.source_tree);
     }
-    while (src_nodes) {
-        next = src_nodes->next;
-        free(src_nodes->mount);
-        free(src_nodes);
-        src_nodes = next;
-    }
 }
 
 static int _compare_stats(void *arg, void *a, void *b)
@@ -1056,15 +982,6 @@ static int _free_source_stats(void *key)
     return 1;
 }
 
-static void _free_event(stats_event_t *event)
-{
-    if (event->source) free(event->source);
-    if (event->name) free(event->name);
-    if (event->value) free(event->value);
-    free(event);
-}
-
-
 
 /* return a list of blocks which contain lines of text. Each line is a mountpoint
  * reference that a slave will use for relaying.  The prepend setting is to indicate
@@ -1149,19 +1066,35 @@ void stats_clear_virtual_mounts (void)
 
 void stats_global_calc (void)
 {
+    event_listener_t *listener;
+    static time_t next_update = 0;
     stats_event_t event;
     char buffer [VAL_BUFSIZE];
-    static time_t next_update = 0;
 
     if (global.time < next_update)
         return;
-    next_update = global.time + 1;
+
+    next_update = global.time + 2;
     build_event (&event, NULL, "outgoing_kbitrate", buffer);
+    event.hidden = STATS_COUNTERS|STATS_HIDDEN;
 
     thread_mutex_lock (&_stats_mutex);
     snprintf (buffer, sizeof(buffer), "%" PRIu64,
-            (int64_t)(global_getrate_avg (global.out_bitrate) * 8 / 1000.0 + 0.5));
+            (int64_t)global_getrate_avg (global.out_bitrate) * 8 / 1000);
     process_event_unlocked (&event);
+    /* retrieve the list of closing down clients */
+    listener = _stats.listeners_removed;
+    _stats.listeners_removed = NULL;
     thread_mutex_unlock (&_stats_mutex);
+
+    /* flush out any closed stats clients */
+    while (listener)
+    {
+        event_listener_t *to_go = listener;
+        listener = listener->next;
+        client_destroy (to_go->client);
+        clear_stats_queue (to_go);
+        free (to_go);
+    }
 }
 
diff --git a/src/stats.h b/src/stats.h
index c66ea692..41e60d58 100644
--- a/src/stats.h
+++ b/src/stats.h
@@ -21,36 +21,17 @@
 #include 
 #include 
 
-
-typedef struct _stats_tag
-{
-    avl_tree *global_tree;
-
-    /* global stats
-    start_time
-    total_users
-    max_users
-    total_sources
-    max_sources
-    total_user_connections
-    total_source_connections
-    */
-
-    avl_tree *source_tree;
-
-    /* stats by source, and for stats
-    start_time
-    total_users
-    max_users
-    */
-
-} stats_t;
+#define STATS_HIDDEN   1
+#define STATS_SLAVE    2
+#define STATS_GENERAL  4
+#define STATS_COUNTERS 8
+#define STATS_PUBLIC   (STATS_GENERAL|STATS_COUNTERS)
+#define STATS_ALL      ~0
 
 void stats_initialize(void);
 void stats_shutdown(void);
 
 void stats_global(ice_config_t *config);
-stats_t *stats_get_stats(void);
 void stats_get_streamlist (char *buffer, size_t remaining);
 refbuf_t *stats_get_streams (int prepend);
 void stats_clear_virtual_mounts (void);
@@ -63,11 +44,11 @@ void stats_event_inc(const char *source, const char *name);
 void stats_event_add(const char *source, const char *name, unsigned long value);
 void stats_event_sub(const char *source, const char *name, unsigned long value);
 void stats_event_dec(const char *source, const char *name);
-void stats_event_hidden (const char *source, const char *name, int hidden);
+void stats_event_hidden (const char *source, const char *name, const char *value, int hidden);
 void stats_event_time (const char *mount, const char *name);
 
 void *stats_connection(void *arg);
-void stats_callback (client_t *client, void *notused);
+void stats_callback (client_t *client, void *mount);
 void stats_global_calc(void);
 
 void stats_transform_xslt(client_t *client, const char *uri);
diff --git a/src/util.c b/src/util.c
index 429017f8..80b7b47c 100644
--- a/src/util.c
+++ b/src/util.c
@@ -47,18 +47,18 @@
 
 struct rate_calc_node
 {
-    time_t time;
+    uint64_t index;
     long value;
-    struct rate_calc_node *next, *prev;
+    struct rate_calc_node *next;
 };
 
 struct rate_calc
 {
     uint64_t total;
-    unsigned int seconds;
-    unsigned int blocks;
-    unsigned int recalc_total;
     struct rate_calc_node *current;
+    unsigned int samples;
+    unsigned int ssec;
+    unsigned int blocks;
 };
 
 
@@ -695,102 +695,90 @@ char *util_conv_string (const char *string, const char *in_charset, const char *
 /* setup a rate block of so many seconds, so that an average can be
  * determined of that range
  */
-struct rate_calc *rate_setup (unsigned int seconds)
+struct rate_calc *rate_setup (unsigned int samples, unsigned int ssec)
 {
     struct rate_calc *calc = calloc (1, sizeof (struct rate_calc));
-    struct rate_calc_node *start = NULL;
-    unsigned int i;
 
-    if (calc == NULL || seconds == 0)
+    if (calc == NULL || samples < 2 || ssec == 0)
     {
         free (calc);
         return NULL;
     }
-    for (i=0 ; icurrent)
-        {
-            calc->current->next = node;
-            node->next = start;
-        }
-        else
-            start = node;
-        node->prev = calc->current;
-        calc->current = node;
-    }
-    calc->current->next = start;
-    start->prev = calc->current;
-    calc->seconds = seconds;
+    calc->samples = samples;
+    calc->ssec = ssec;
     return calc;
 }
 
-/* */
-static void rate_recalc_total (struct rate_calc *calc)
-{
-    int i;
-    struct rate_calc_node *p = calc->current->prev;
-    calc->total = 0;
-    for (i=calc->blocks-1; i; i--)
-    {
-        calc->total += p->value;
-        p = p->prev;
-    }
-}
 
 /* add a value to sampled data, t is used to determine which sample
  * block the sample goes into.
  */
-void rate_add (struct rate_calc *calc, long value, time_t t)
+void rate_add (struct rate_calc *calc, long value, uint64_t sid) 
 {
-    if (t == 0)
+    if (calc->current == NULL || sid != calc->current->index)
     {
-        calc->blocks = 0;
-        return;
-    }
-    if (t != calc->current->time)
-    {
-        if (calc->recalc_total)
+        if (calc->blocks == calc->samples)
         {
-            /* here we keep the number of blocks and recalculate the total */
+            calc->total += calc->current->value;
             calc->current = calc->current->next;
-            rate_recalc_total (calc);
-            calc->recalc_total--;
+            calc->total -= calc->current->value;
+            calc->current->value = value;
+            calc->current->index = sid;
+            return;
         }
         else
         {
-            /* common case */
-            calc->total += calc->current->value;
-            calc->current = calc->current->next;
-            if (calc->blocks == calc->seconds)
-                calc->total -= calc->current->value;
+            struct rate_calc_node *node = calloc (1, sizeof (*node));
+            node->index = sid;
+            calc->blocks++;
+            if (calc->current)
+            {
+                node->next = calc->current->next;
+                calc->current->next = node;
+                calc->total += calc->current->value;
+            }
             else
-                calc->blocks++;
+            {
+                node->next = node;
+            }
+            calc->current = node;
         }
-        calc->current->value = 0;
-        calc->current->time = t;
     }
     calc->current->value += value;
 }
 
-
 /* return the average sample value over all the blocks except the 
  * current one, as that may be incomplete
  */
 long rate_avg (struct rate_calc *calc)
 {
+    uint64_t range;
+
     if (calc == NULL || calc->blocks < 2)
         return 0;
-    return (long)(calc->total / (calc->blocks-1));
+    range = (calc->current->index - calc->current->next->index) / calc->ssec;
+    if (range < 1)
+        range = 1;
+    return (long)(calc->total / range);
 }
 
 /* reduce the samples used to calculate average */
-void rate_reduce (struct rate_calc *calc, unsigned long count)
+void rate_reduce (struct rate_calc *calc, unsigned int count)
 {
-    if (calc && count < calc->blocks && count >= 2)
+    if (calc && count < calc->blocks)
     {
-        calc->recalc_total = count*2;
-        calc->blocks = count;
+        struct rate_calc_node *list = calc->current->next;
+        for (; calc->blocks > count; calc->blocks--)
+        {
+            struct rate_calc_node *to_go = list;
+            list = to_go->next;
+            calc->total -= to_go->value;
+            free (to_go);
+        }
+        if (calc->blocks)
+            calc->current->next = list;
+        else
+            calc->current = NULL;
     }
 }
 
diff --git a/src/util.h b/src/util.h
index f1453a13..4da6cd33 100644
--- a/src/util.h
+++ b/src/util.h
@@ -58,11 +58,11 @@ struct tm *localtime_r (const time_t *timep, struct tm *result);
 #endif
 char *util_conv_string (const char *string, const char *in_charset, const char *out_charset);
 
-struct rate_calc *rate_setup (unsigned int seconds);
-void rate_add (struct rate_calc *calc, long value, time_t t);
+struct rate_calc *rate_setup (unsigned int samples, unsigned int ssec);
+void rate_add (struct rate_calc *calc, long value, uint64_t t);
 long rate_avg (struct rate_calc *calc);
 void rate_free (struct rate_calc *calc);
-void rate_reduce (struct rate_calc *calc, unsigned long count);
+void rate_reduce (struct rate_calc *calc, unsigned int count);
 
 int get_line(FILE *file, char *buf, size_t siz);
 
diff --git a/src/yp.c b/src/yp.c
index 521b225a..7f87e9e4 100644
--- a/src/yp.c
+++ b/src/yp.c
@@ -925,12 +925,10 @@ void yp_remove (const char *mount)
 void yp_touch (const char *mount)
 {
     struct yp_server *server = (struct yp_server *)active_yps;
-    time_t trigger;
     ypdata_t *search_list = NULL;
 
     thread_rwlock_rlock (&yp_lock);
-    /* do update in 3 secs, give stats chance to update */
-    trigger = time(NULL) + 3;
+
     if (server)
         search_list = server->mounts;
 
@@ -945,9 +943,9 @@ void yp_touch (const char *mount)
                 search_list = yp->next;
                 continue;
             }
-            /* only force if touch */
-            if (yp->process == do_yp_touch)
-                yp->next_update = trigger;
+            /* don't update the directory if there is a touch scheduled soon */
+            if (yp->process == do_yp_touch && now + yp->touch_interval - yp->next_update > 60)
+                yp->next_update = now;
         }
         server = server->next;
         if (server)
diff --git a/web/status.xsl b/web/status.xsl
index 5e15f7ef..65228396 100755
--- a/web/status.xsl
+++ b/web/status.xsl
@@ -79,6 +79,17 @@
 
 
Stream URL:
Preview: frame preview
Preview: frame preview
Current Song: -
diff --git a/win32/icecast.dsp b/win32/icecast.dsp index 100b3504..2b1956af 100644 --- a/win32/icecast.dsp +++ b/win32/icecast.dsp @@ -363,10 +363,6 @@ SOURCE=..\src\md5.h # End Source File # Begin Source File -SOURCE=..\src\os.h -# End Source File -# Begin Source File - SOURCE=..\src\refbuf.h # End Source File # Begin Source File diff --git a/win32/icecast2.iss b/win32/icecast2.iss index 5c2832ab..ac920939 100644 --- a/win32/icecast2.iss +++ b/win32/icecast2.iss @@ -3,7 +3,7 @@ [Setup] AppName=Icecast2-KH -AppVerName=Icecast v2.3.2-kh1 +AppVerName=Icecast v2.3.2-kh2 AppPublisherURL=http://www.icecast.org AppSupportURL=http://www.icecast.org AppUpdatesURL=http://www.icecast.org @@ -13,7 +13,7 @@ AllowNoIcons=yes LicenseFile=..\COPYING InfoAfterFile=..\README OutputDir=. -OutputBaseFilename=icecast2_win32_v2.3.2-kh1_setup +OutputBaseFilename=icecast2_win32_v2.3.2-kh2_setup WizardImageFile=icecast2logo2.bmp WizardImageStretch=no ; uncomment the following line if you want your installation to run on NT 3.51 too.