diff --git a/client/README.txt b/client/README.txt
index 119ffa93c3fabc50e789d943ebc63d1ff61a7c9a..4108c58676ed121d0ffd30a512c2f283ebb4209a 100644
--- a/client/README.txt
+++ b/client/README.txt
@@ -12,7 +12,7 @@ Install from source
 
 2. Clone the Git repository:
 
-    $ git clone https://git.autistici.org/djrandom.git
+    $ git clone http://git.autistici.org/djrandom.git
 
 3. Install the client. Python will automatically download all the
    required dependencies:
@@ -35,10 +35,24 @@ Install from package
 2. Client packages are available:
 
    OSX:
-   - https://git.autistici.org/p/djrandom/dist/DJRandomUploader.zip
+   - http://git.autistici.org/p/djrandom/dist/DJRandomUploader.zip
 
    Linux (Debian/Ubuntu):
-   - https://git.autistici.org/p/djrandom/dist/djrandom-client_0.2.3_all.deb
+   - Add the following repository to your list of APT sources (you
+     can simply create /etc/apt/sources.list.d/djrandom.list for
+     that if your apt is recent enough):
+
+       deb http://git.autistici.org/p/djrandom/debian unstable main
+
+     Install the repository GPG key with:
+
+       $ wget -O- http://git.autistici.org/p/djrandom/debian/repo.key \
+         | sudo apt-key add -
+
+     Then update and install the djrandom-client package:
+
+       $ sudo apt-get update
+       $ sudo apt-get install djrandom-client
 
 
 Running the client
diff --git a/client/debian/changelog b/client/debian/changelog
index 12677484571072ef791c56f3317c178f13f86d80..a9d3aa2edf83a40c7716599cbbe0266a0ff8958e 100644
--- a/client/debian/changelog
+++ b/client/debian/changelog
@@ -1,3 +1,16 @@
+djrandom-client (0.2.7-1) unstable; urgency=low
+
+  * Add an init script to run it as a daemon.
+
+ -- ale <ale@incal.net>  Sun, 25 Feb 2012 00:59:43 +0000
+
+djrandom-client (0.2.7) unstable; urgency=low
+
+  * Add --exclude option.
+  * Ignore metadata and hidden files.
+
+ -- ale <ale@incal.net>  Sat, 24 Feb 2012 23:26:12 +0000
+
 djrandom-client (0.2.5) unstable; urgency=low
 
   * Fix bandwidth throttling for HTTPS urls.
diff --git a/client/debian/control b/client/debian/control
index 5b8d5c03150052ea8ba02606c167a536dc12e49b..456a35ad1d6e89af0dd3eeaae80cd5fe84d37069 100644
--- a/client/debian/control
+++ b/client/debian/control
@@ -8,7 +8,7 @@ Standards-Version: 3.8.0.1
 
 Package: djrandom-client
 Architecture: all
-Depends: ${python:Depends}, python-pyinotify
+Depends: ${python:Depends}
 Description: DJRandom uploader client.
  Uploads your music in the background.
 
diff --git a/client/debian/djrandom-client.default b/client/debian/djrandom-client.default
new file mode 100644
index 0000000000000000000000000000000000000000..e72bbba04981b0133eed6d5ab55d2842cd24977d
--- /dev/null
+++ b/client/debian/djrandom-client.default
@@ -0,0 +1,4 @@
+
+# Set this to 'true' to enable the daemon.
+ENABLED=false
+
diff --git a/client/debian/djrandom-client.init b/client/debian/djrandom-client.init
new file mode 100755
index 0000000000000000000000000000000000000000..3227884a71b25cf5d5c3c8b2981904b9735a3d67
--- /dev/null
+++ b/client/debian/djrandom-client.init
@@ -0,0 +1,65 @@
+#!/bin/bash
+#
+# Start/stop the DJRandom client.
+#
+### BEGIN INIT INFO
+# Provides:          djrandom
+# Required-Start:    $network $local_fs
+# Required-Stop:
+# Should-Start:
+# Should-Stop:
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: DJRandom Client
+# Description:       DJRandom Client
+### END INIT INFO
+
+
+NAME=djrandom-client
+DESCR="DJRandom client"
+DAEMON=/usr/bin/djrandom-client
+RUNDIR=/var/run/djrandom
+PIDFILE=${RUNDIR}/${NAME}.pid
+USER=nobody
+OPTIONS=
+ENABLED=false
+
+test -e /etc/default/${NAME} && . /etc/default/${NAME}
+
+test -x ${DAEMON} || exit 0
+
+if [ "${ENABLED}" != "true" ]; then
+  echo "DJRandom will not start as a daemon."
+  echo "Set ENABLED=true in /etc/default/${NAME} to enable."
+  exit 0
+fi
+
+case "$1" in
+start)
+	test -d ${RUNDIR} || mkdir -p ${RUNDIR}
+	if [ -n "${USER}" ]; then
+		chown ${USER} ${RUNDIR}
+		OPTIONS="--user ${USER} ${OPTIONS}"
+	fi
+	OPTIONS="--skip_version_check ${OPTIONS}"
+
+	echo -n "Starting ${DESCR}... "
+	start-stop-daemon --start --pidfile ${PIDFILE} \
+		   	--exec ${DAEMON} -- ${OPTIONS}
+	echo "ok"
+	;;
+stop)
+	echo -n "Stopping ${DESCR}... "
+	start-stop-daemon --stop \
+	    	--pidfile ${PIDFILE} && rm -f ${PIDFILE}
+	echo "ok"
+	;;
+restart)
+	$0 stop
+	sleep 3
+	$0 start
+	;;
+esac
+
+exit 0
+
diff --git a/client/djrandom_client/client.py b/client/djrandom_client/client.py
index 3a15226a8ce6d759243a00cc9e5c4fb21a297b4b..dac2f4a7a67619b8a2f0cf6dc9a77a149ba6e264 100644
--- a/client/djrandom_client/client.py
+++ b/client/djrandom_client/client.py
@@ -14,20 +14,11 @@ from djrandom_client import upload
 from djrandom_client import utils
 from djrandom_client import throttle
 
-# Detect platform and selectively enable inotify/fsevents watchers.
-watcher_support = False
-try:
-    if platform.system() == 'Darwin':
-        import djrandom_client.osx_watcher as watcher
-        watcher_support = True
-    elif platform.system() == 'Linux':
-        import djrandom_client.linux_watcher as watcher
-        watcher_support = True
-except ImportError:
-    pass
-
 log = logging.getLogger(__name__)
 
+# How often we rescan the filesystem looking for new files.
+SCAN_DELAY = 1800
+
 
 class FullScan(threading.Thread):
     """Do a recursive directory scan when starting."""
@@ -55,11 +46,10 @@ class FullScan(threading.Thread):
             time.sleep(delay)
 
 
-def run_client(server_url, music_dir, excludes, api_key, run_once, bwlimit,
-               enable_watcher):
+def run_client(server_url, music_dir, excludes, api_key, run_once, bwlimit):
     log.debug('settings: server=%s, music_dir=%s, api_key=%s, '
-              'bwlimit=%s, watcher=%s' % (
-        server_url, music_dir, api_key, bwlimit, str(enable_watcher)))
+              'bwlimit=%s' % (
+        server_url, music_dir, api_key, bwlimit))
 
     # Warn if we're running without a bandwidth limit.
     bwlimit = int(bwlimit)
@@ -82,19 +72,11 @@ def run_client(server_url, music_dir, excludes, api_key, run_once, bwlimit,
             exclude_prefix += '/'
         abs_excludes.append(os.path.realpath(exclude_prefix))
 
-    if enable_watcher and not watcher_support:
-        log.warn('inotify/fsevents support not enabled on this platform')
-        enable_watcher = False
-
     upl = upload.Uploader(server_url.rstrip('/'), api_key, abs_excludes)
     upl.setDaemon(True)
 
     # Start the full filesystem scan in the background.
-    if enable_watcher:
-        scan_delay = 3 * 86400
-    else:
-        scan_delay = 9600
-    scan = FullScan(music_dir, upl.queue, scan_delay, run_once)
+    scan = FullScan(music_dir, upl.queue, SCAN_DELAY, run_once)
     scan.setDaemon(True)
 
     if not run_once:
@@ -104,15 +86,8 @@ def run_client(server_url, music_dir, excludes, api_key, run_once, bwlimit,
             # Set 'idle' I/O scheduling class, we won't disturb other programs.
             os.system('/usr/bin/ionice -c 3 -p %d' % os.getpid())
 
-        # Start the live filesystem watcher.
-        if enable_watcher:
-            wtch = watcher.Watcher(music_dir, upl.queue)
-
         # Install termination signal handlers.
         def _cleanup(signum, frame):
-            if enable_watcher:
-                log.info('stopping watcher...')
-                wtch.stop()
             log.info('got signal %d, exiting...' % signum)
             upl.stop()
             sys.exit(0)
@@ -152,8 +127,6 @@ def main():
     parser.add_option('--bwlimit', type='int', default=0,
                       help='Upload bandwidth limit, kilobytes/s (default: '
                       'unlimited)')
-    parser.add_option('--no_realtime_watch', action='store_true',
-                      help='Monitor music_dir in realtime')
     parser.add_option('--skip_version_check', action='store_true')
     daemonize.add_standard_options(parser)
     utils.read_config_defaults(
@@ -171,13 +144,9 @@ def main():
     if not opts.skip_version_check and utils.check_version():
         print >>sys.stderr, 'A new release is available! Please update.'
 
-    # Reading from the configuration file will set this variable to
-    # a string, convert it back into boolean.
-    do_realtime = not opts.no_realtime_watch
-
     daemonize.daemonize(opts, run_client, 
                         (opts.server_url, opts.music_dir, opts.exclude, 
-                         opts.api_key, opts.once, opts.bwlimit, do_realtime))
+                         opts.api_key, opts.once, opts.bwlimit))
 
 
 if __name__ == '__main__':
diff --git a/client/djrandom_client/filescan.py b/client/djrandom_client/filescan.py
index e8410387ba2218ff5578c1e18d5e10237c40affb..a209c08a2d3d4c7b7c05c5f21d16bdc94a99b1b8 100644
--- a/client/djrandom_client/filescan.py
+++ b/client/djrandom_client/filescan.py
@@ -1,11 +1,19 @@
 import os
 
+DIR_BLACKLIST = set(['.AppleDouble'])
+
 
 def recursive_scan(basedir, queue):
     n = 0
-    for root, dirs, files in os.walk(basedir):
+    for root, dirs, files in os.walk(basedir, topdown=True,
+                                     followlinks=True):
+        prune_dirs = [x for x in dirs
+                      if (x in DIR_BLACKLIST or x.startswith('.'))]
+        for pdir in prune_dirs:
+            dirs.remove(pdir)
         for filename in files:
-            if filename.lower().endswith('.mp3'):
+            if (filename.lower().endswith('.mp3') and
+                not filename.startswith('.')):
                 path = os.path.join(root, filename)
                 queue.put(path)
                 n += 1
diff --git a/client/djrandom_client/linux_watcher.py b/client/djrandom_client/linux_watcher.py
deleted file mode 100644
index 797e1cf55cc1fb371d687a257916624162158b03..0000000000000000000000000000000000000000
--- a/client/djrandom_client/linux_watcher.py
+++ /dev/null
@@ -1,24 +0,0 @@
-import logging
-import pyinotify
-
-log = logging.getLogger(__name__)
-
-
-class Watcher(pyinotify.ProcessEvent):
-
-    def __init__(self, base, queue):
-        self.queue = queue
-        self.wm = pyinotify.WatchManager()
-        self.notifier = pyinotify.ThreadedNotifier(self.wm, self)
-        self.notifier.setDaemon(True)
-        self.notifier.start()
-        self.wm.add_watch(base, pyinotify.IN_CLOSE_WRITE, rec=True)
-
-    def stop(self):
-        self.notifier.stop()
-
-    def process_IN_CLOSE(self, event):
-        if event.pathname.lower().endswith('.mp3'):
-            log.debug('event in %s: %x' % (event.pathname, event.mask))
-            self.queue.put(event.pathname)
-
diff --git a/client/djrandom_client/osx_watcher.py b/client/djrandom_client/osx_watcher.py
deleted file mode 100644
index e13ac863ac2bfbbb92b4bbc9d4f4fbaf6ec5afeb..0000000000000000000000000000000000000000
--- a/client/djrandom_client/osx_watcher.py
+++ /dev/null
@@ -1,23 +0,0 @@
-import logging
-from fsevents import Observer, Stream
-from djrandom_client import filescan
-
-log = logging.getLogger(__name__)
-
-
-class Watcher(object):
-
-    def __init__(self, base, queue):
-        self.queue = queue
-        self.observer = Observer()
-        self.observer.schedule(Stream(self.callback, base))
-        self.observer.setDaemon(True)
-        self.observer.start()
-
-    def callback(self, subpath, mask):
-        log.debug('event in %s: %x' % (subpath, mask))
-        filescan.directory_scan(subpath, self.queue)
-
-    def stop(self):
-        self.observer.stop()
-
diff --git a/client/djrandom_client/test/test_client.py b/client/djrandom_client/test/test_client.py
index 55a38eca1784fa098c3e0f655a9b960a351ae81b..638be66009107a34538dc3eac1e4614319ce4fe1 100644
--- a/client/djrandom_client/test/test_client.py
+++ b/client/djrandom_client/test/test_client.py
@@ -161,12 +161,12 @@ class ClientOptionsTest(ClientRunner, mox.MoxTestBase):
                                          '/my/music',
                                          [],
                                          'KEY',
-                                         True, 10, False))
+                                         True, 10))
 
         self.mox.ReplayAll()
         self._Run(['--api_key=KEY', '--server_url=http://server/receiver',
                    '--music_dir=/my/music', '--bwlimit=10',
-                   '--once', '--no_realtime_watch'])
+                   '--once'])
 
 
 class RunClientTest(mox.MoxTestBase):
@@ -201,8 +201,7 @@ class RunClientTest(mox.MoxTestBase):
             [],
             'KEY',
             True,
-            150,
-            False)
+            150)
 
 
 if __name__ == '__main__':
diff --git a/client/djrandom_client/upload.py b/client/djrandom_client/upload.py
index 391b679cbfa4ade1676223417eeadbf504cf3afd..7b2d5fb47ff4cc869534be4d2bf5e49c56e447c8 100644
--- a/client/djrandom_client/upload.py
+++ b/client/djrandom_client/upload.py
@@ -113,6 +113,7 @@ class Uploader(threading.Thread):
         if not result:
             raise UploadError('server error')
         self.stats.incr('uploaded_files')
+        self.stats.incr('uploaded_bytes', os.path.getsize(path))
         log.info('successfully uploaded %s (%s)' % (path, sha1))
 
     def _should_exclude(self, path):
diff --git a/client/djrandom_client/version.py b/client/djrandom_client/version.py
index 09e82fd65d33e7118abcad132c1875e1c9e2c0d9..e954bd9e835c26f787d572dd12de5caabd4617ae 100644
--- a/client/djrandom_client/version.py
+++ b/client/djrandom_client/version.py
@@ -1 +1 @@
-VERSION = '0.2.6'
+VERSION = '0.2.7'
diff --git a/client/osx-ui/DJRandomUploader/ProcessController.m b/client/osx-ui/DJRandomUploader/ProcessController.m
index a16033daabb0bd19478d09efcabd6f6d6df2963b..b346f3c3f86fc868e4e6fc8de39a0c721e7840a0 100644
--- a/client/osx-ui/DJRandomUploader/ProcessController.m
+++ b/client/osx-ui/DJRandomUploader/ProcessController.m
@@ -54,7 +54,6 @@
         NSArray *args = [NSArray 
                          arrayWithObjects:@"--api_key", curApiKey, @"--music_dir", 
                          curMusicFolder, @"--debug", @"--foreground", 
-                         @"--no_realtime_watch",
                          nil];
         if ([curBwLimit intValue] > 0) {
             args = [args arrayByAddingObjectsFromArray:[NSArray 
diff --git a/client/setup.py b/client/setup.py
index 3c7a13393fe2ca985a7d6ee988c50564cccb33bc..84367a0e9829928b711ed3bd6f8455c596496b1e 100644
--- a/client/setup.py
+++ b/client/setup.py
@@ -8,13 +8,11 @@ from djrandom_client import version
 from setuptools import setup, find_packages
 import platform
 if platform.system() == 'Darwin':
-    platform_requires = ['MacFSEvents']
     extra_options = {
       'app': ['djrandom_client/client.py'],
       'setup_requires': ['py2app']
     }
 else:
-    platform_requires = ['pyinotify']
     extra_options = {}
 
 setup(
@@ -24,7 +22,6 @@ setup(
   author="ale",
   author_email="ale@incal.net",
   url="http://git.autistici.org/git/djrandom.git",
-  install_requires=platform_requires,
   zip_safe=True,
   packages=find_packages(),
   entry_points={