Autocomplete working in query_editor plugin
[unfold.git] / manifold / util / daemon.py
diff --git a/manifold/util/daemon.py b/manifold/util/daemon.py
new file mode 100644 (file)
index 0000000..2e5d760
--- /dev/null
@@ -0,0 +1,343 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Daemon: superclass used to implement a daemon easily
+#
+# Copyright (C)2009-2012, UPMC Paris Universitas
+# Authors:
+#   Marc-Olivier Buob <marc-olivier.buob@lip6.fr>
+
+# see also: http://www.jejik.com/files/examples/daemon3x.py
+
+# This is used to import the daemon package instead of the local module which is
+# named identically...
+from __future__ import absolute_import
+
+from manifold.util.singleton    import Singleton
+from manifold.util.log          import Log
+from manifold.util.options      import Options
+
+import atexit, os, signal, lockfile, logging, sys
+
+class Daemon(object):
+    __metaclass__ = Singleton
+
+    DEFAULTS = {
+        # Running
+        "uid"                 : os.getuid(),
+        "gid"                 : os.getgid(),
+        "working_directory"   : "/",
+        "debugmode"           : False,
+        "no_daemon"           : False,
+        "pid_filename"        : "/var/run/%s.pid" % Options().get_name()
+    }
+    
+    #-------------------------------------------------------------------------
+    # Checks 
+    #-------------------------------------------------------------------------
+
+    def check_python_daemon(self):
+        """
+        \brief Check whether python-daemon is properly installed
+        \return True if everything is file, False otherwise
+        """
+        # http://www.python.org/dev/peps/pep-3143/    
+        ret = False 
+        try:
+            import daemon
+            getattr(daemon, "DaemonContext")
+            ret = True 
+        except AttributeError, e:
+            print e
+            # daemon and python-daemon conflict with each other
+            Log.critical("Please install python-daemon instead of daemon. Remove daemon first.")
+        except ImportError:
+            Log.critical("Please install python-daemon - easy_install python-daemon.")
+        return ret
+
+    #------------------------------------------------------------------------
+    # Initialization 
+    #------------------------------------------------------------------------
+
+    def make_handler_rsyslog(self, rsyslog_host, rsyslog_port, log_level):
+        """
+        \brief (Internal usage) Prepare logging via rsyslog
+        \param rsyslog_host The hostname of the rsyslog server
+        \param rsyslog_port The port of the rsyslog server
+        \param log_level Log level
+        """
+        # Prepare the handler
+        shandler = handlers.SysLogHandler(
+            (rsyslog_host, rsyslog_port),
+            facility = handlers.SysLogHandler.LOG_DAEMON
+        )
+
+        # The log file must remain open while daemonizing 
+        self.files_to_keep.append(shandler.socket)
+        self.prepare_handler(shandler, log_level)
+        return shandler
+
+    def make_handler_locallog(self, log_filename, log_level):
+        """
+        \brief (Internal usage) Prepare local logging
+        \param log_filename The file in which we write the logs
+        \param log_level Log level
+        """
+        # Create directory in which we store the log file
+        log_dir = os.path.dirname(log_filename)
+        if not os.path.exists(log_dir):
+            try:
+                os.makedirs(log_dir)
+            except OSError, why: 
+                log_error("OS error: %s" % why)
+
+        # Prepare the handler
+        shandler = logging.handlers.RotatingFileHandler(
+            log_filename,
+            backupCount = 0
+        )
+
+        # The log file must remain open while daemonizing 
+        self.files_to_keep.append(shandler.stream)
+        self.prepare_handler(shandler, log_level)
+        return shandler
+
+    def prepare_handler(self, shandler, log_level):
+        """
+        \brief (Internal usage)
+        \param shandler Handler used to log information
+        \param log_level Log level
+        """
+        shandler.setLevel(log_level)
+        formatter = logging.Formatter("%(asctime)s: %(name)s: %(levelname)s %(message)s")
+        shandler.setFormatter(formatter)
+        self.log.addHandler(shandler)
+        self.log.setLevel(getattr(logging, log_level, logging.INFO))
+
+    def __init__(
+        self,
+        #daemon_name,
+        terminate_callback = None
+        #uid               = os.getuid(),
+        #gid               = os.getgid(),
+        #working_directory = "/",
+        #pid_filename      = None,
+        #no_daemon         = False,
+        #debug             = False,
+        #log               = None,        # logging.getLogger("plop")
+        #rsyslog_host      = "localhost", # Pass None if no rsyslog server
+        #rsyslog_port      = 514,
+        #log_file          = None,
+        #log_level         = logging.INFO
+   ):
+        """
+        \brief Constructor
+        \param daemon_name The name of the daemon
+        \param uid UID used to run the daemon
+        \param gid GID used to run the daemon
+        \param working_directory Working directory used to run the daemon.
+            Example: /var/lib/foo/
+        \param pid_filename Absolute path of the PID file
+            Example: /var/run/foo.pid
+            (ignored if no_daemon == True)
+        \param no_daemon Do not detach the daemon from the terminal
+        \param debug Run daemon in debug mode
+        \param log The logger, pass None if unused
+            Example: logging.getLogger('foo'))
+        \param rsyslog_host Rsyslog hostname, pass None if unused.
+            If rsyslog_host is set to None, log are stored locally
+        \param rsyslog_port Rsyslog port
+        \param log_file Absolute path of the local log file.
+            Example: /var/log/foo.log)
+        \param log_level Log level
+            Example: logging.INFO
+        """
+
+        # Daemon parameters
+        #self.daemon_name        = daemon_name
+        self.terminate_callback = terminate_callback
+        #Options().uid               = uid
+        #Options().gid               = gid
+        #Options().working_directory = working_directory
+        #self.pid_filename      = None if no_daemon else pid_filename
+        #Options().no_daemon         = no_daemon
+        #Options().lock_file         = None
+        #Options().debug             = debug
+        #self.log               = log 
+        #self.rsyslog_host      = rsyslog_host
+        #self.rsyslog_port      = rsyslog_port 
+        #self.log_file          = log_file 
+        #self.log_level         = log_level
+
+        # Reference which file descriptors must remain opened while
+        # daemonizing (for instance the file descriptor related to
+        # the logger)
+        self.files_to_keep = []
+
+        # Initialize self.log (require self.files_to_keep)
+        #if self.log: # for debugging by using stdout, log may be equal to None
+        #    if rsyslog_host:
+        #        shandler = self.make_handler_rsyslog(
+        #            rsyslog_host,
+        #            rsyslog_port,
+        #            log_level
+        #        )
+        #    elif log_file:
+        #        shandler = self.make_handler_locallog(
+        #            log_file,
+        #            log_level
+        #        )
+
+    @classmethod
+    def init_options(self):
+        opt = Options()
+
+        opt.add_option(
+            "--uid", dest = "uid",
+            help = "UID used to run the dispatcher.",
+            default = self.DEFAULTS['uid']
+        )
+        opt.add_option(
+            "--gid", dest = "gid",
+            help = "GID used to run the dispatcher.",
+            default = self.DEFAULTS['gid']
+        )
+        opt.add_option(
+            "-w", "--working-directory", dest = "working_directory",
+            help = "Working directory.",
+            default = self.DEFAULTS['working_directory']
+        )
+        opt.add_option(
+            "-D", "--debugmode", action = "store_false", dest = "debugmode",
+            help = "Daemon debug mode (useful for developers).",
+            default = self.DEFAULTS['debugmode']
+        )
+        opt.add_option(
+            "-n", "--no-daemon", action = "store_true", dest = "no_daemon",
+            help = "Run as daemon (detach from terminal).",
+            default = self.DEFAULTS["no_daemon"]
+        )
+        opt.add_option(
+            "-i", "--pid-file", dest = "pid_filename",
+            help = "Absolute path to the pid-file to use when running as daemon.",
+            default = self.DEFAULTS['pid_filename']
+        )
+
+        
+
+    #------------------------------------------------------------------------
+    # Daemon stuff 
+    #------------------------------------------------------------------------
+
+    def remove_pid_file(self):
+        """
+        \brief Remove the pid file (internal usage)
+        """
+        # The lock file is implicitely released while removing the pid file
+        Log.debug("Removing %s" % Options().pid_filename)
+        if os.path.exists(Options().pid_filename) == True:
+            os.remove(Options().pid_filename)
+
+    def make_pid_file(self):
+        """
+        \brief Create a pid file in which we store the PID of the daemon if needed 
+        """
+        if Options().pid_filename and Options().no_daemon == False:
+            atexit.register(self.remove_pid_file)
+            file(Options().pid_filename, "w+").write("%s\n" % str(os.getpid()))
+
+    def get_pid_from_pid_file(self):
+        """
+        \brief Retrieve the PID of the daemon thanks to the pid file.
+        \return None if the pid file is not readable or does not exists
+        """
+        pid = None
+        if Options().pid_filename:
+            try:
+                f_pid = file(Options().pid_filename, "r")
+                pid = int(f_pid.read().strip())
+                f_pid.close()
+            except IOError:
+                pid = None
+        return pid
+
+    def make_lock_file(self):
+        """
+        \brief Prepare the lock file required to manage the pid file
+            Initialize Options().lock_file
+        """
+        if Options().pid_filename and Options().no_daemon == False:
+            Log.debug("Daemonizing using pid file '%s'" % Options().pid_filename)
+            Options().lock_file = lockfile.FileLock(Options().pid_filename)
+            if Options().lock_file.is_locked() == True:
+                log_error("'%s' is already running ('%s' is locked)." % (Options().get_name(), Options().pid_filename))
+                self.terminate()
+            Options().lock_file.acquire()
+        else:
+            Options().lock_file = None
+
+    def start(self):
+        """
+        \brief Start the daemon
+        """
+        # Check whether daemon module is properly installed
+        if self.check_python_daemon() == False:
+            self.terminate()
+        import daemon
+
+        # Prepare Options().lock_file
+        self.make_lock_file()
+
+        # Prepare the daemon context
+        dcontext = daemon.DaemonContext(
+            detach_process     = (not Options().no_daemon),
+            working_directory  = Options().working_directory,
+            pidfile            = Options().lock_file if not Options().no_daemon else None,
+            stdin              = sys.stdin,
+            stdout             = sys.stdout,
+            stderr             = sys.stderr,
+            uid                = Options().uid,
+            gid                = Options().gid,
+            files_preserve     = Log().files_to_keep
+        )
+
+        # Prepare signal handling to stop properly if the daemon is killed 
+        # Note that signal.SIGKILL can't be handled:
+        # http://crunchtools.com/unixlinux-signals-101/
+        dcontext.signal_map = {
+            signal.SIGTERM : self.signal_handler,
+            signal.SIGQUIT : self.signal_handler,
+            signal.SIGINT  : self.signal_handler
+        }
+
+        if Options().debugmode == True:
+            self.main()
+        else:
+            with dcontext:
+                self.make_pid_file()
+                try:
+                    self.main()
+                except Exception, why:
+                    Log.error("Unhandled exception in start: %s" % why)
+
+    def signal_handler(self, signal_id, frame):
+        """
+        \brief Stop the daemon (signal handler)
+            The lockfile is implicitly released by the daemon package
+        \param signal_id The integer identifying the signal
+            (see also "man 7 signal")
+            Example: 15 if the received signal is signal.SIGTERM
+        \param frame
+        """
+        self.terminate()
+
+    def stop(self):
+        Log.debug("Stopping '%s'" % self.daemon_name)
+
+    def terminate(self):
+        if self.terminate_callback:
+            self.terminate_callback()
+        else:
+            sys.exit(0)
+
+Daemon.init_options()