# -*- coding: utf-8; mode: python; -*- # # GStreamer Development Utilities # # Copyright (C) 2007 René Stadler # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the Free # Software Foundation; either version 3 of the License, or (at your option) # any later version. # # This program 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 General Public License for # more details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . """GStreamer Development Utilities Common Main module.""" import sys import os import traceback from operator import attrgetter import logging import locale import gettext from gettext import gettext as _, ngettext import gi from gi.repository import GLib from gi.repository import GObject from gi.repository import Gtk class ExceptionHandler (object): exc_types = (Exception,) priority = 50 inherit_fork = True _handling_exception = False def __call__(self, exc_type, exc_value, exc_traceback): raise NotImplementedError( "derived classes need to override this method") class DefaultExceptionHandler (ExceptionHandler): exc_types = (BaseException,) priority = 0 inherit_fork = True def __init__(self, excepthook): ExceptionHandler.__init__(self) self.excepthook = excepthook def __call__(self, *exc_info): return self.excepthook(*exc_info) class ExitOnInterruptExceptionHandler (ExceptionHandler): exc_types = (KeyboardInterrupt,) priority = 100 inherit_fork = False exit_status = 2 def __call__(self, *args): print("Interrupt caught, exiting.", file=sys.stderr) sys.exit(self.exit_status) class MainLoopWrapper (ExceptionHandler): priority = 95 inherit_fork = False def __init__(self, enter, exit): ExceptionHandler.__init__(self) self.exc_info = (None,) * 3 self.enter = enter self.exit = exit def __call__(self, *exc_info): self.exc_info = exc_info self.exit() def run(self): ExceptHookManager.register_handler(self) try: self.enter() finally: ExceptHookManager.unregister_handler(self) if self.exc_info != (None,) * 3: # Re-raise unhandled exception that occured while running the loop. exc_type, exc_value, exc_tb = self.exc_info raise exc_value class ExceptHookManagerClass (object): def __init__(self): self._in_forked_child = False self.handlers = [] def setup(self): if sys.excepthook == self.__excepthook: raise ValueError("already set up") hook = sys.excepthook self.__instrument_excepthook() self.__instrument_fork() self.register_handler(DefaultExceptionHandler(hook)) def shutdown(self): if sys.excepthook != self.__excepthook: raise ValueError("not set up") self.__restore_excepthook() self.__restore_fork() def __instrument_excepthook(self): hook = sys.excepthook self._original_excepthook = hook sys.excepthook = self.__excepthook def __restore_excepthook(self): sys.excepthook = self._original_excepthook def __instrument_fork(self): try: fork = os.fork except AttributeError: # System has no fork() system call. self._original_fork = None else: self._original_fork = fork os.fork = self.__fork def __restore_fork(self): if not hasattr(os, "fork"): return os.fork = self._original_fork def entered_forked_child(self): self._in_forked_child = True for handler in tuple(self.handlers): if not handler.inherit_fork: self.handlers.remove(handler) def register_handler(self, handler): if self._in_forked_child and not handler.inherit_fork: return self.handlers.append(handler) def unregister_handler(self, handler): self.handlers.remove(handler) def __fork(self): pid = self._original_fork() if pid == 0: # Child process. self.entered_forked_child() return pid def __excepthook(self, exc_type, exc_value, exc_traceback): for handler in sorted(self.handlers, key=attrgetter("priority"), reverse=True): if handler._handling_exception: continue for type_ in handler.exc_types: if issubclass(exc_type, type_): break else: continue handler._handling_exception = True handler(exc_type, exc_value, exc_traceback) # Not using try...finally on purpose here. If the handler itself # fails with an exception, this prevents recursing into it again. handler._handling_exception = False return else: from warnings import warn warn("ExceptHookManager: unhandled %r" % (exc_value,), RuntimeWarning, stacklevel=2) ExceptHookManager = ExceptHookManagerClass() class PathsBase (object): data_dir = None icon_dir = None locale_dir = None @classmethod def setup_installed(cls, data_prefix): """Set up paths for running from a regular installation.""" pass @classmethod def setup_uninstalled(cls, source_dir): """Set up paths for running 'uninstalled' (i.e. directly from the source dist).""" pass @classmethod def ensure_setup(cls): """If paths are still not set up, try to set from a fallback.""" if cls.data_dir is None: source_dir = os.path.dirname( os.path.dirname(os.path.abspath(__file__))) cls.setup_uninstalled(source_dir) def __new__(cls): raise RuntimeError("do not create instances of this class -- " "use the class object directly") class PathsProgramBase (PathsBase): program_name = None @classmethod def setup_installed(cls, data_prefix): if cls.program_name is None: raise NotImplementedError( "derived classes need to set program_name attribute") cls.data_dir = os.path.join(data_prefix, cls.program_name) cls.icon_dir = os.path.join(data_prefix, "icons") cls.locale_dir = os.path.join(data_prefix, "locale") @classmethod def setup_uninstalled(cls, source_dir): """Set up paths for running 'uninstalled' (i.e. directly from the source dist).""" # This is essential: The GUI module needs to find the .glade file. cls.data_dir = os.path.join(source_dir, "data") # The locale data might be missing if "setup.py build" wasn't run. cls.locale_dir = os.path.join(source_dir, "build", "mo") # Not setting icon_dir. It is not useful since we don't employ the # needed directory structure in the source dist. def _init_excepthooks(): ExceptHookManager.setup() ExceptHookManager.register_handler(ExitOnInterruptExceptionHandler()) def _init_paths(paths): paths.ensure_setup() def _init_locale(gettext_domain=None): if Paths.locale_dir and gettext_domain is not None: try: locale.setlocale(locale.LC_ALL, "") except locale.Error as exc: from warnings import warn warn("locale error: %s" % (exc,), RuntimeWarning, stacklevel=2) Paths.locale_dir = None else: gettext.bindtextdomain(gettext_domain, Paths.locale_dir) gettext.textdomain(gettext_domain) gettext.bind_textdomain_codeset(gettext_domain, "UTF-8") def _init_logging(level): if level == "none": return mapping = {"debug": logging.DEBUG, "info": logging.INFO, "warning": logging.WARNING, "error": logging.ERROR, "critical": logging.CRITICAL} logging.basicConfig(level=mapping[level], format='%(asctime)s.%(msecs)03d %(levelname)8s %(name)20s: %(message)s', datefmt='%H:%M:%S') logger = logging.getLogger("main") logger.debug("logging at level %s", logging.getLevelName(level)) logger.info("using Python %i.%i.%i %s %i", *sys.version_info) def _init_log_option(parser): choices = ["none", "debug", "info", "warning", "error", "critical"] parser.add_option("--log-level", "-l", type="choice", choices=choices, action="store", dest="log_level", default="none", help=_("Enable logging, possible values: ") + ", ".join(choices)) return parser def main(main_function, option_parser, gettext_domain=None, paths=None): # FIXME: global Paths Paths = paths _init_excepthooks() _init_paths(paths) _init_locale(gettext_domain) parser = _init_log_option(option_parser) options, args = option_parser.parse_args() _init_logging(options.log_level) try: main_function(args) finally: logging.shutdown()