#!/usr/bin/python import logging import optparse import os import platform import signal import sys import threading import time from djrandom_client import daemonize from djrandom_client import filescan 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 = True if platform.system() == 'Darwin': import djrandom_client.osx_watcher as watcher elif platform.system() == 'Linux': import djrandom_client.linux_watcher as watcher else: watcher_support = False log = logging.getLogger(__name__) class FullScan(threading.Thread): """Do a recursive directory scan when starting.""" def __init__(self, basedir, queue, base_delay, exit_when_done=False): threading.Thread.__init__(self) self.basedir = basedir self.base_delay = base_delay self.queue = queue self.exit_when_done = exit_when_done def run(self): while True: delay = self.base_delay try: filescan.recursive_scan(self.basedir, self.queue) except Exception, e: log.error('Error in file scan: %s' % e) # Retry 30 minutes after an error. delay = 1800 if self.exit_when_done: self.queue.put(None) break time.sleep(delay) def run_client(server_url, music_dir, api_key, run_once, bwlimit, enable_watcher): if bwlimit: throttle.set_rate_limit(bwlimit) # Warn on this condition, but don't die -- the directory might exist later! if not os.path.isdir(music_dir): log.error('The music_dir you specified does not exist') 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) 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.setDaemon(True) if not run_once: # Run at a lower priority. os.nice(10) if os.path.exists('/usr/bin/ionice'): # 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) signal.signal(signal.SIGINT, _cleanup) signal.signal(signal.SIGTERM, _cleanup) upl.start() scan.start() if run_once: upl.join() else: # It turns out that, even if we set up signal handlers in this # same main thread, they won't be executed if we're blocking on # a mutex (such as 'upl.join()' for example)... so we do this # silly idle loop in the main thread just to ensure that we # catch SIGTERM and SIGINT. while True: time.sleep(3600) def main(): parser = optparse.OptionParser(usage='%prog [<OPTIONS>]') parser.add_option('--api_key', help='Your API key') parser.add_option('--once', action='store_true', help='Scan music_dir once and then exit') parser.add_option('--music_dir', default='~/Music', help='Path to your music directory') parser.add_option('--server_url', default='https://djrandom.incal.net/receiver', help='URL to the API endpoint') parser.add_option('--bwlimit', type='int', help='Bandwidth limit (in KBps, default unlimited)') parser.add_option('--no_realtime_watch', action='store_true', help='Monitor music_dir in realtime') daemonize.add_standard_options(parser) utils.read_config_defaults( parser, os.path.join(os.getenv('HOME'), '.djrandom.conf')) parser.set_default( 'pidfile', os.path.join(os.getenv('HOME'), '.djrandom.pid')) opts, args = parser.parse_args() if not opts.api_key: parser.error('You must specify an API Key') music_dir = os.path.expanduser(opts.music_dir) if args: parser.error('Too many arguments') # Perform a version check. if 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, music_dir, opts.api_key, opts.once, opts.bwlimit, do_realtime)) if __name__ == '__main__': main()