diff --git a/Headphones.py b/Headphones.py index 1982818d..facb2191 100644 --- a/Headphones.py +++ b/Headphones.py @@ -16,6 +16,7 @@ import os, sys, locale import time +import signal from lib.configobj import ConfigObj @@ -27,7 +28,10 @@ try: import argparse except ImportError: import lib.argparse as argparse - + +signal.signal(signal.SIGINT, headphones.sig_handler) +signal.signal(signal.SIGTERM, headphones.sig_handler) + def main(): @@ -36,10 +40,10 @@ def main(): headphones.FULL_PATH = os.path.abspath(sys.executable) else: headphones.FULL_PATH = os.path.abspath(__file__) - + headphones.PROG_DIR = os.path.dirname(headphones.FULL_PATH) headphones.ARGS = sys.argv[1:] - + # From sickbeard headphones.SYS_PLATFORM = sys.platform headphones.SYS_ENCODING = None @@ -53,7 +57,7 @@ def main(): # for OSes that are poorly configured I'll just force UTF-8 if not headphones.SYS_ENCODING or headphones.SYS_ENCODING in ('ANSI_X3.4-1968', 'US-ASCII', 'ASCII'): headphones.SYS_ENCODING = 'UTF-8' - + # Set up and gather command line arguments parser = argparse.ArgumentParser(description='Music add-on for SABnzbd+') @@ -65,55 +69,73 @@ def main(): parser.add_argument('--config', help='Specify a config file to use') parser.add_argument('--nolaunch', action='store_true', help='Prevent browser from launching on startup') parser.add_argument('--pidfile', help='Create a pid file (only relevant when running as a daemon)') - + args = parser.parse_args() if args.verbose: headphones.VERBOSE = 2 elif args.quiet: headphones.VERBOSE = 0 - + if args.daemon: - headphones.DAEMON=True - headphones.VERBOSE = 0 - if args.pidfile : - headphones.PIDFILE = args.pidfile + if sys.platform == 'win32': + print "Daemonize not supported under Windows, starting normally" + else: + headphones.DAEMON=True + headphones.VERBOSE = False + + if args.pidfile: + headphones.PIDFILE = str(args.pidfile) + + # If the pidfile already exists, headphones may still be running, so exit + if os.path.exists(headphones.PIDFILE): + sys.exit("PID file '" + headphones.PIDFILE + "' already exists. Exiting.") + + # The pidfile is only useful in daemon mode, make sure we can write the file properly + if headphones.DAEMON: + headphones.CREATEPID = True + try: + file(headphones.PIDFILE, 'w').write("pid\n") + except IOError, e: + raise SystemExit("Unable to write PID file: %s [%d]" % (e.strerror, e.errno)) + else: + logger.warn("Not running in daemon mode. PID file creation disabled.") if args.datadir: headphones.DATA_DIR = args.datadir else: headphones.DATA_DIR = headphones.PROG_DIR - + if args.config: headphones.CONFIG_FILE = args.config else: headphones.CONFIG_FILE = os.path.join(headphones.DATA_DIR, 'config.ini') - + # Try to create the DATA_DIR if it doesn't exist if not os.path.exists(headphones.DATA_DIR): try: os.makedirs(headphones.DATA_DIR) except OSError: raise SystemExit('Could not create data directory: ' + headphones.DATA_DIR + '. Exiting....') - + # Make sure the DATA_DIR is writeable if not os.access(headphones.DATA_DIR, os.W_OK): raise SystemExit('Cannot write to the data directory: ' + headphones.DATA_DIR + '. Exiting...') - + # Put the database in the DATA_DIR headphones.DB_FILE = os.path.join(headphones.DATA_DIR, 'headphones.db') - + headphones.CFG = ConfigObj(headphones.CONFIG_FILE, encoding='utf-8') - + # Read config & start logging headphones.initialize() - + if headphones.DAEMON: if sys.platform == "win32": print "Daemonize not supported under Windows, starting normally" else: headphones.daemonize() - + #configure the connection to the musicbrainz database headphones.mb.startmb() @@ -123,8 +145,8 @@ def main(): logger.info('Starting Headphones on forced port: %i' % http_port) else: http_port = int(headphones.HTTP_PORT) - - # Try to start the server. + + # Try to start the server. webstart.initialize({ 'http_port': http_port, 'http_host': headphones.HTTP_HOST, @@ -133,15 +155,15 @@ def main(): 'http_username': headphones.HTTP_USERNAME, 'http_password': headphones.HTTP_PASSWORD, }) - + logger.info('Starting Headphones on port: %i' % http_port) - + if headphones.LAUNCH_BROWSER and not args.nolaunch: headphones.launch_browser(headphones.HTTP_HOST, http_port, headphones.HTTP_ROOT) - + # Start the background threads headphones.start() - + while True: if not headphones.SIGNAL: try: @@ -156,9 +178,9 @@ def main(): headphones.shutdown(restart=True) else: headphones.shutdown(restart=True, update=True) - + headphones.SIGNAL = None - + return if __name__ == "__main__": diff --git a/headphones/__init__.py b/headphones/__init__.py index e402482f..0983a37a 100644 --- a/headphones/__init__.py +++ b/headphones/__init__.py @@ -41,6 +41,7 @@ SYS_ENCODING = None VERBOSE = 1 DAEMON = False +CREATEPID = False PIDFILE= None SCHED = Scheduler() @@ -577,42 +578,34 @@ def daemonize(): # Do first fork try: - pid = os.fork() - if pid == 0: - pass - else: - # Exit the parent process - logger.debug('Forking once...') - os._exit(0) + pid = os.fork() # @UndefinedVariable - only available in UNIX + if pid != 0: + sys.exit(0) except OSError, e: - sys.exit("1st fork failed: %s [%d]" % (e.strerror, e.errno)) + raise RuntimeError("1st fork failed: %s [%d]" % (e.strerror, e.errno)) os.setsid() - # Do second fork + # Make sure I can read my own files and shut out others + prev = os.umask(0) # @UndefinedVariable - only available in UNIX + os.umask(prev and int('077', 8)) + + # Make the child a session-leader by detaching from the terminal try: - pid = os.fork() - if pid > 0: - logger.debug('Forking twice...') - os._exit(0) # Exit second parent process + pid = os.fork() # @UndefinedVariable - only available in UNIX + if pid != 0: + sys.exit(0) except OSError, e: - sys.exit("2nd fork failed: %s [%d]" % (e.strerror, e.errno)) + raise RuntimeError("2nd fork failed: %s [%d]" % (e.strerror, e.errno)) - os.chdir("/") - os.umask(0) + dev_null = file('/dev/null', 'r') + os.dup2(dev_null.fileno(), sys.stdin.fileno()) - si = open('/dev/null', "r") - so = open('/dev/null', "a+") - se = open('/dev/null', "a+") - - os.dup2(si.fileno(), sys.stdin.fileno()) - os.dup2(so.fileno(), sys.stdout.fileno()) - os.dup2(se.fileno(), sys.stderr.fileno()) - - pid = os.getpid() + pid = str(os.getpid()) logger.info('Daemonized to PID: %s' % pid) - if PIDFILE: - logger.info('Writing PID %s to %s' % (pid, PIDFILE)) + + if CREATEPID: + logger.info("Writing PID " + pid + " to " + str(PIDFILE)) file(PIDFILE, 'w').write("%s\n" % pid) def launch_browser(host, port, root): @@ -827,6 +820,11 @@ def start(): started = True +def sig_handler(signum=None, frame=None): + if type(signum) != type(None): + logger.info("Signal %i caught, saving and exiting..." % int(signum)) + shutdown() + def dbcheck(): conn=sqlite3.connect(DB_FILE) @@ -1013,6 +1011,7 @@ def shutdown(restart=False, update=False): if not restart and not update: logger.info('Headphones is shutting down...') + if update: logger.info('Headphones is updating...') try: @@ -1020,7 +1019,7 @@ def shutdown(restart=False, update=False): except Exception, e: logger.warn('Headphones failed to update: %s. Restarting.' % e) - if PIDFILE : + if CREATEPID : logger.info ('Removing pidfile %s' % PIDFILE) os.remove(PIDFILE)