Update
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
# -*- coding: utf-8; mode: python; -*-
|
||||
#
|
||||
# GStreamer Development Utilities
|
||||
#
|
||||
# Copyright (C) 2007 René Stadler <mail@renestadler.de>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""GStreamer Development Utilities Common Data module."""
|
||||
|
||||
import gi
|
||||
|
||||
from gi.repository import GObject
|
||||
|
||||
|
||||
class Dispatcher (object):
|
||||
|
||||
def __call__(self, iterator):
|
||||
|
||||
raise NotImplementedError("derived classes must override this method")
|
||||
|
||||
def cancel(self):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class DefaultDispatcher (Dispatcher):
|
||||
|
||||
def __call__(self, iterator):
|
||||
|
||||
for x in iterator:
|
||||
pass
|
||||
|
||||
|
||||
class GSourceDispatcher (Dispatcher):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
Dispatcher.__init__(self)
|
||||
|
||||
self.source_id = None
|
||||
|
||||
def __call__(self, iterator):
|
||||
|
||||
if self.source_id is not None:
|
||||
GObject.source_remove(self.source_id)
|
||||
|
||||
def iteration():
|
||||
r = iterator.__next__()
|
||||
if not r:
|
||||
self.source_id = None
|
||||
return r
|
||||
|
||||
self.source_id = GObject.idle_add(
|
||||
iteration, priority=GObject.PRIORITY_LOW)
|
||||
|
||||
def cancel(self):
|
||||
|
||||
if self.source_id is None:
|
||||
return
|
||||
|
||||
GObject.source_remove(self.source_id)
|
||||
self.source_id = None
|
||||
@@ -0,0 +1,528 @@
|
||||
# -*- coding: utf-8; mode: python; -*-
|
||||
#
|
||||
# GStreamer Development Utilities
|
||||
#
|
||||
# Copyright (C) 2007 René Stadler <mail@renestadler.de>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""GStreamer Development Utilities Common GUI module."""
|
||||
|
||||
import os
|
||||
|
||||
import logging
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gdk
|
||||
from gi.types import GObjectMeta
|
||||
|
||||
import GstDebugViewer
|
||||
from GstDebugViewer.Common import utils
|
||||
from .generictreemodel import GenericTreeModel
|
||||
|
||||
|
||||
def widget_add_popup_menu(widget, menu, button=3):
|
||||
|
||||
def popup_callback(widget, event):
|
||||
|
||||
if event.button == button:
|
||||
menu.popup(
|
||||
None, None, None, None, event.button, event.get_time())
|
||||
return False
|
||||
|
||||
widget.connect("button-press-event", popup_callback)
|
||||
|
||||
|
||||
class Actions (dict):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
dict.__init__(self)
|
||||
|
||||
self.groups = {}
|
||||
|
||||
def __getattr__(self, name):
|
||||
|
||||
try:
|
||||
return self[name]
|
||||
except KeyError:
|
||||
if "_" in name:
|
||||
try:
|
||||
return self[name.replace("_", "-")]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
raise AttributeError("no action with name %r" % (name,))
|
||||
|
||||
def add_group(self, group):
|
||||
|
||||
name = group.props.name
|
||||
if name in self.groups:
|
||||
raise ValueError("already have a group named %s", name)
|
||||
self.groups[name] = group
|
||||
for action in group.list_actions():
|
||||
self[action.props.name] = action
|
||||
|
||||
|
||||
class Widgets (dict):
|
||||
|
||||
def __init__(self, builder):
|
||||
|
||||
widgets = (obj for obj in builder.get_objects()
|
||||
if isinstance(obj, Gtk.Buildable))
|
||||
# Gtk.Widget.get_name() shadows out the GtkBuildable interface method
|
||||
# of the same name, hence calling the unbound interface method here:
|
||||
items = ((Gtk.Buildable.get_name(w), w,) for w in widgets)
|
||||
|
||||
dict.__init__(self, items)
|
||||
|
||||
def __getattr__(self, name):
|
||||
|
||||
try:
|
||||
return self[name]
|
||||
except KeyError:
|
||||
if "_" in name:
|
||||
try:
|
||||
return self[name.replace("_", "-")]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
raise AttributeError("no widget with name %r" % (name,))
|
||||
|
||||
|
||||
class WidgetFactory (object):
|
||||
|
||||
def __init__(self, directory):
|
||||
|
||||
self.directory = directory
|
||||
|
||||
def get_builder(self, filename):
|
||||
|
||||
builder_filename = os.path.join(self.directory, filename)
|
||||
|
||||
builder = Gtk.Builder()
|
||||
builder.set_translation_domain(GstDebugViewer.GETTEXT_DOMAIN)
|
||||
builder.add_from_file(builder_filename)
|
||||
|
||||
return builder
|
||||
|
||||
def make(self, filename, widget_name, autoconnect=None):
|
||||
|
||||
builder = self.get_builder(filename)
|
||||
|
||||
if autoconnect is not None:
|
||||
builder.connect_signals(autoconnect)
|
||||
|
||||
return Widgets(builder)
|
||||
|
||||
def make_one(self, filename, widget_name):
|
||||
|
||||
builder = self.get_builder(filename)
|
||||
|
||||
return builder.get_object(widget_name)
|
||||
|
||||
|
||||
class UIFactory (object):
|
||||
|
||||
def __init__(self, ui_filename, actions=None):
|
||||
|
||||
self.filename = ui_filename
|
||||
if actions:
|
||||
self.action_groups = actions.groups
|
||||
else:
|
||||
self.action_groups = ()
|
||||
|
||||
def make(self, extra_actions=None):
|
||||
|
||||
ui_manager = Gtk.UIManager()
|
||||
for action_group in list(self.action_groups.values()):
|
||||
ui_manager.insert_action_group(action_group, 0)
|
||||
if extra_actions:
|
||||
for action_group in extra_actions.groups:
|
||||
ui_manager.insert_action_group(action_group, 0)
|
||||
ui_manager.add_ui_from_file(self.filename)
|
||||
ui_manager.ensure_update()
|
||||
|
||||
return ui_manager
|
||||
|
||||
|
||||
class MetaModel (GObjectMeta):
|
||||
|
||||
"""Meta class for easy setup of gtk tree models.
|
||||
|
||||
Looks for a class attribute named `columns' which must be set to a
|
||||
sequence of the form name1, type1, name2, type2, ..., where the
|
||||
names are strings. This metaclass adds the following attributes
|
||||
to created classes:
|
||||
|
||||
cls.column_types = (type1, type2, ...)
|
||||
cls.column_ids = (0, 1, ...)
|
||||
cls.name1 = 0
|
||||
cls.name2 = 1
|
||||
...
|
||||
|
||||
Example: A Gtk.ListStore derived model can use
|
||||
|
||||
columns = ("COL_NAME", str, "COL_VALUE", str)
|
||||
|
||||
and use this in __init__:
|
||||
|
||||
GObject.GObject.__init__ (self, *self.column_types)
|
||||
|
||||
Then insert data like this:
|
||||
|
||||
self.set (self.append (),
|
||||
self.COL_NAME, "spam",
|
||||
self.COL_VALUE, "ham")
|
||||
"""
|
||||
|
||||
def __init__(cls, name, bases, dict):
|
||||
|
||||
super(MetaModel, cls).__init__(name, bases, dict)
|
||||
|
||||
spec = tuple(cls.columns)
|
||||
|
||||
column_names = spec[::2]
|
||||
column_types = spec[1::2]
|
||||
column_indices = list(range(len(column_names)))
|
||||
|
||||
for col_index, col_name, in zip(column_indices, column_names):
|
||||
setattr(cls, col_name, col_index)
|
||||
|
||||
cls.column_types = column_types
|
||||
cls.column_ids = tuple(column_indices)
|
||||
|
||||
|
||||
class Manager (object):
|
||||
|
||||
"""GUI Manager base class."""
|
||||
|
||||
@classmethod
|
||||
def iter_item_classes(cls):
|
||||
|
||||
msg = "%s class does not support manager item class access"
|
||||
raise NotImplementedError(msg % (cls.__name__,))
|
||||
|
||||
@classmethod
|
||||
def find_item_class(self, **kw):
|
||||
|
||||
return self.__find_by_attrs(self.iter_item_classes(), kw)
|
||||
|
||||
def iter_items(self):
|
||||
|
||||
msg = "%s object does not support manager item access"
|
||||
raise NotImplementedError(msg % (type(self).__name__,))
|
||||
|
||||
def find_item(self, **kw):
|
||||
|
||||
return self.__find_by_attrs(self.iter_items(), kw)
|
||||
|
||||
@staticmethod
|
||||
def __find_by_attrs(i, kw):
|
||||
|
||||
from operator import attrgetter
|
||||
|
||||
if len(kw) != 1:
|
||||
raise ValueError("need exactly one keyword argument")
|
||||
|
||||
attr, value = list(kw.items())[0]
|
||||
getter = attrgetter(attr)
|
||||
|
||||
for item in i:
|
||||
if getter(item) == value:
|
||||
return item
|
||||
else:
|
||||
raise KeyError("no item such that item.%s == %r" % (attr, value,))
|
||||
|
||||
|
||||
class StateString (object):
|
||||
|
||||
"""Descriptor for binding to StateSection classes."""
|
||||
|
||||
def __init__(self, option, default=None):
|
||||
|
||||
self.option = option
|
||||
self.default = default
|
||||
|
||||
def __get__(self, section, section_class=None):
|
||||
|
||||
import configparser
|
||||
|
||||
if section is None:
|
||||
return self
|
||||
|
||||
try:
|
||||
return self.get(section)
|
||||
except (configparser.NoSectionError,
|
||||
configparser.NoOptionError,):
|
||||
return self.get_default(section)
|
||||
|
||||
def __set__(self, section, value):
|
||||
|
||||
import configparser
|
||||
|
||||
self.set(section, value)
|
||||
|
||||
def get(self, section):
|
||||
|
||||
return section.get(self)
|
||||
|
||||
def get_default(self, section):
|
||||
|
||||
return self.default
|
||||
|
||||
def set(self, section, value):
|
||||
|
||||
if value is None:
|
||||
value = ""
|
||||
|
||||
section.set(self, str(value))
|
||||
|
||||
|
||||
class StateBool (StateString):
|
||||
|
||||
"""Descriptor for binding to StateSection classes."""
|
||||
|
||||
def get(self, section):
|
||||
|
||||
return section.state._parser.getboolean(section._name, self.option)
|
||||
|
||||
|
||||
class StateInt (StateString):
|
||||
|
||||
"""Descriptor for binding to StateSection classes."""
|
||||
|
||||
def get(self, section):
|
||||
|
||||
return section.state._parser.getint(section._name, self.option)
|
||||
|
||||
|
||||
class StateInt4 (StateString):
|
||||
|
||||
"""Descriptor for binding to StateSection classes. This implements storing
|
||||
a tuple of 4 integers."""
|
||||
|
||||
def get(self, section):
|
||||
|
||||
value = StateString.get(self, section)
|
||||
|
||||
try:
|
||||
l = value.split(",")
|
||||
if len(l) != 4:
|
||||
return None
|
||||
else:
|
||||
return tuple((int(v) for v in l))
|
||||
except (AttributeError, TypeError, ValueError,):
|
||||
return None
|
||||
|
||||
def set(self, section, value):
|
||||
|
||||
if value is None:
|
||||
svalue = ""
|
||||
elif len(value) != 4:
|
||||
raise ValueError("value needs to be a 4-sequence, or None")
|
||||
else:
|
||||
svalue = ", ".join((str(v) for v in value))
|
||||
|
||||
return StateString.set(self, section, svalue)
|
||||
|
||||
|
||||
class StateItem (StateString):
|
||||
|
||||
"""Descriptor for binding to StateSection classes. This implements storing
|
||||
a class controlled by a Manager class."""
|
||||
|
||||
def __init__(self, option, manager_class, default=None):
|
||||
|
||||
StateString.__init__(self, option, default=default)
|
||||
|
||||
self.manager = manager_class
|
||||
|
||||
def get(self, section):
|
||||
|
||||
value = SectionString.get(self, section)
|
||||
|
||||
if not value:
|
||||
return None
|
||||
|
||||
return self.parse_item(value)
|
||||
|
||||
def set(self, section, value):
|
||||
|
||||
if value is None:
|
||||
svalue = ""
|
||||
else:
|
||||
svalue = value.name
|
||||
|
||||
StateString.set(self, section, svalue)
|
||||
|
||||
def parse_item(self, value):
|
||||
|
||||
name = value.strip()
|
||||
|
||||
try:
|
||||
return self.manager.find_item_class(name=name)
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
|
||||
class StateItemList (StateItem):
|
||||
|
||||
"""Descriptor for binding to StateSection classes. This implements storing
|
||||
an ordered set of Manager items."""
|
||||
|
||||
def get(self, section):
|
||||
|
||||
value = StateString.get(self, section)
|
||||
|
||||
if not value:
|
||||
return []
|
||||
|
||||
classes = []
|
||||
for name in value.split(","):
|
||||
item_class = self.parse_item(name)
|
||||
if item_class is None:
|
||||
continue
|
||||
if not item_class in classes:
|
||||
classes.append(item_class)
|
||||
|
||||
return classes
|
||||
|
||||
def get_default(self, section):
|
||||
|
||||
default = StateItem.get_default(self, section)
|
||||
if default is None:
|
||||
return []
|
||||
else:
|
||||
return default
|
||||
|
||||
def set(self, section, value):
|
||||
|
||||
if value is None:
|
||||
svalue = ""
|
||||
else:
|
||||
svalue = ", ".join((v.name for v in value))
|
||||
|
||||
StateString.set(self, section, svalue)
|
||||
|
||||
|
||||
class StateSection (object):
|
||||
|
||||
_name = None
|
||||
|
||||
def __init__(self, state):
|
||||
|
||||
self.state = state
|
||||
|
||||
if self._name is None:
|
||||
raise NotImplementedError(
|
||||
"subclasses must override the _name attribute")
|
||||
|
||||
def get(self, state_string):
|
||||
|
||||
return self.state._parser.get(self._name, state_string.option)
|
||||
|
||||
def set(self, state_string, value):
|
||||
|
||||
import configparser
|
||||
|
||||
parser = self.state._parser
|
||||
|
||||
try:
|
||||
parser.set(self._name, state_string.option, value)
|
||||
except configparser.NoSectionError:
|
||||
parser.add_section(self._name)
|
||||
parser.set(self._name, state_string.option, value)
|
||||
|
||||
|
||||
class State (object):
|
||||
|
||||
def __init__(self, filename, old_filenames=()):
|
||||
|
||||
import configparser
|
||||
|
||||
self.sections = {}
|
||||
|
||||
self._filename = filename
|
||||
self._parser = configparser.RawConfigParser()
|
||||
success = self._parser.read([filename])
|
||||
if not success:
|
||||
for old_filename in old_filenames:
|
||||
success = self._parser.read([old_filename])
|
||||
if success:
|
||||
break
|
||||
|
||||
def add_section_class(self, section_class):
|
||||
|
||||
self.sections[section_class._name] = section_class(self)
|
||||
|
||||
def save(self):
|
||||
|
||||
with utils.SaveWriteFile(self._filename, "wt") as fp:
|
||||
self._parser.write(fp)
|
||||
|
||||
|
||||
class WindowState (object):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.logger = logging.getLogger("ui.window-state")
|
||||
|
||||
self.is_maximized = False
|
||||
|
||||
def attach(self, window, state):
|
||||
|
||||
self.window = window
|
||||
self.state = state
|
||||
|
||||
self.window.connect("window-state-event",
|
||||
self.handle_window_state_event)
|
||||
|
||||
geometry = self.state.geometry
|
||||
if geometry:
|
||||
self.window.move(*geometry[:2])
|
||||
self.window.set_default_size(*geometry[2:])
|
||||
|
||||
if self.state.maximized:
|
||||
self.logger.debug("initially maximized")
|
||||
self.window.maximize()
|
||||
|
||||
def detach(self):
|
||||
|
||||
window = self.window
|
||||
|
||||
self.state.maximized = self.is_maximized
|
||||
if not self.is_maximized:
|
||||
position = tuple(window.get_position())
|
||||
size = tuple(window.get_size())
|
||||
self.state.geometry = position + size
|
||||
|
||||
self.window.disconnect_by_func(self.handle_window_state_event)
|
||||
self.window = None
|
||||
|
||||
def handle_window_state_event(self, window, event):
|
||||
|
||||
if not event.changed_mask & Gdk.WindowState.MAXIMIZED:
|
||||
return
|
||||
|
||||
if event.new_window_state & Gdk.WindowState.MAXIMIZED:
|
||||
self.logger.debug("maximized")
|
||||
self.is_maximized = True
|
||||
else:
|
||||
self.logger.debug("unmaximized")
|
||||
self.is_maximized = False
|
||||
@@ -0,0 +1,366 @@
|
||||
# -*- coding: utf-8; mode: python; -*-
|
||||
#
|
||||
# GStreamer Development Utilities
|
||||
#
|
||||
# Copyright (C) 2007 René Stadler <mail@renestadler.de>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""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_devenv(cls, source_dir):
|
||||
"""Set up paths for running the development environment
|
||||
(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_devenv(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_devenv(cls, source_dir):
|
||||
"""Set up paths for running the development environment
|
||||
(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()
|
||||
@@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8; mode: python; -*-
|
||||
#
|
||||
# GStreamer Development Utilities
|
||||
#
|
||||
# Copyright (C) 2007 René Stadler <mail@renestadler.de>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""GStreamer Development Utilities Common package."""
|
||||
|
||||
from . import Data
|
||||
from . import GUI
|
||||
from . import Main
|
||||
from . import utils
|
||||
@@ -0,0 +1,420 @@
|
||||
# -*- Mode: Python; py-indent-offset: 4 -*-
|
||||
# generictreemodel - GenericTreeModel implementation for pygtk compatibility.
|
||||
# Copyright (C) 2013 Simon Feltman
|
||||
#
|
||||
# generictreemodel.py: GenericTreeModel implementation for pygtk compatibility
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library 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
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
# System
|
||||
import sys
|
||||
import random
|
||||
import collections
|
||||
import ctypes
|
||||
|
||||
# GObject
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gtk
|
||||
|
||||
|
||||
class _CTreeIter(ctypes.Structure):
|
||||
_fields_ = [('stamp', ctypes.c_int),
|
||||
('user_data', ctypes.c_void_p),
|
||||
('user_data2', ctypes.c_void_p),
|
||||
('user_data3', ctypes.c_void_p)]
|
||||
|
||||
@classmethod
|
||||
def from_iter(cls, iter):
|
||||
offset = sys.getsizeof(object()) # size of PyObject_HEAD
|
||||
return ctypes.POINTER(cls).from_address(id(iter) + offset)
|
||||
|
||||
|
||||
def _get_user_data_as_pyobject(iter):
|
||||
citer = _CTreeIter.from_iter(iter)
|
||||
return ctypes.cast(citer.contents.user_data, ctypes.py_object).value
|
||||
|
||||
|
||||
def handle_exception(default_return):
|
||||
"""Returns a function which can act as a decorator for wrapping exceptions and
|
||||
returning "default_return" upon an exception being thrown.
|
||||
|
||||
This is used to wrap Gtk.TreeModel "do_" method implementations so we can return
|
||||
a proper value from the override upon an exception occurring with client code
|
||||
implemented by the "on_" methods.
|
||||
"""
|
||||
def decorator(func):
|
||||
def wrapped_func(*args, **kargs):
|
||||
try:
|
||||
return func(*args, **kargs)
|
||||
except BaseException:
|
||||
# Use excepthook directly to avoid any printing to the screen
|
||||
# if someone installed an except hook.
|
||||
sys.excepthook(*sys.exc_info())
|
||||
return default_return
|
||||
return wrapped_func
|
||||
return decorator
|
||||
|
||||
|
||||
class GenericTreeModel(GObject.GObject, Gtk.TreeModel):
|
||||
|
||||
"""A base implementation of a Gtk.TreeModel for python.
|
||||
|
||||
The GenericTreeModel eases implementing the Gtk.TreeModel interface in Python.
|
||||
The class can be subclassed to provide a TreeModel implementation which works
|
||||
directly with Python objects instead of iterators.
|
||||
|
||||
All of the on_* methods should be overridden by subclasses to provide the
|
||||
underlying implementation a way to access custom model data. For the purposes of
|
||||
this API, all custom model data supplied or handed back through the overridable
|
||||
API will use the argument names: node, parent, and child in regards to user data
|
||||
python objects.
|
||||
|
||||
The create_tree_iter, set_user_data, invalidate_iters, iter_is_valid methods are
|
||||
available to help manage Gtk.TreeIter objects and their Python object references.
|
||||
|
||||
GenericTreeModel manages a pool of user data nodes that have been used with iters.
|
||||
This pool stores a references to user data nodes as a dictionary value with the
|
||||
key being the integer id of the data. This id is what the Gtk.TreeIter objects
|
||||
use to reference data in the pool.
|
||||
References will be removed from the pool when the model is deleted or explicitly
|
||||
by using the optional "node" argument to the "row_deleted" method when notifying
|
||||
the model of row deletion.
|
||||
"""
|
||||
|
||||
leak_references = GObject.Property(default=True, type=bool,
|
||||
blurb="If True, strong references to user data attached to iters are "
|
||||
"stored in a dictionary pool (default). Otherwise the user data is "
|
||||
"stored as a raw pointer to a python object without a reference.")
|
||||
|
||||
#
|
||||
# Methods
|
||||
#
|
||||
def __init__(self):
|
||||
"""Initialize. Make sure to call this from derived classes if overridden."""
|
||||
super(GenericTreeModel, self).__init__()
|
||||
self.stamp = 0
|
||||
|
||||
#: Dictionary of (id(user_data): user_data), used when leak-refernces=False
|
||||
self._held_refs = dict()
|
||||
|
||||
# Set initial stamp
|
||||
self.invalidate_iters()
|
||||
|
||||
def iter_depth_first(self):
|
||||
"""Depth-first iteration of the entire TreeModel yielding the python nodes."""
|
||||
stack = collections.deque([None])
|
||||
while stack:
|
||||
it = stack.popleft()
|
||||
if it is not None:
|
||||
yield self.get_user_data(it)
|
||||
children = [self.iter_nth_child(it, i)
|
||||
for i in range(self.iter_n_children(it))]
|
||||
stack.extendleft(reversed(children))
|
||||
|
||||
def invalidate_iter(self, iter):
|
||||
"""Clear user data and its reference from the iter and this model."""
|
||||
iter.stamp = 0
|
||||
if iter.user_data:
|
||||
if iter.user_data in self._held_refs:
|
||||
del self._held_refs[iter.user_data]
|
||||
iter.user_data = None
|
||||
|
||||
def invalidate_iters(self):
|
||||
"""
|
||||
This method invalidates all TreeIter objects associated with this custom tree model
|
||||
and frees their locally pooled references.
|
||||
"""
|
||||
self.stamp = random.randint(-2147483648, 2147483647)
|
||||
self._held_refs.clear()
|
||||
|
||||
def iter_is_valid(self, iter):
|
||||
"""
|
||||
:Returns:
|
||||
True if the gtk.TreeIter specified by iter is valid for the custom tree model.
|
||||
"""
|
||||
return iter.stamp == self.stamp
|
||||
|
||||
def get_user_data(self, iter):
|
||||
"""Get the user_data associated with the given TreeIter.
|
||||
|
||||
GenericTreeModel stores arbitrary Python objects mapped to instances of Gtk.TreeIter.
|
||||
This method allows to retrieve the Python object held by the given iterator.
|
||||
"""
|
||||
if self.leak_references:
|
||||
return self._held_refs[iter.user_data]
|
||||
else:
|
||||
return _get_user_data_as_pyobject(iter)
|
||||
|
||||
def set_user_data(self, iter, user_data):
|
||||
"""Applies user_data and stamp to the given iter.
|
||||
|
||||
If the models "leak_references" property is set, a reference to the
|
||||
user_data is stored with the model to ensure we don't run into bad
|
||||
memory problems with the TreeIter.
|
||||
"""
|
||||
iter.user_data = id(user_data)
|
||||
|
||||
if user_data is None:
|
||||
self.invalidate_iter(iter)
|
||||
else:
|
||||
iter.stamp = self.stamp
|
||||
if self.leak_references:
|
||||
self._held_refs[iter.user_data] = user_data
|
||||
|
||||
def create_tree_iter(self, user_data):
|
||||
"""Create a Gtk.TreeIter instance with the given user_data specific for this model.
|
||||
|
||||
Use this method to create Gtk.TreeIter instance instead of directly calling
|
||||
Gtk.Treeiter(), this will ensure proper reference managment of wrapped used_data.
|
||||
"""
|
||||
iter = Gtk.TreeIter()
|
||||
self.set_user_data(iter, user_data)
|
||||
return iter
|
||||
|
||||
def _create_tree_iter(self, data):
|
||||
"""Internal creation of a (bool, TreeIter) pair for returning directly
|
||||
back to the view interfacing with this model."""
|
||||
if data is None:
|
||||
return (False, None)
|
||||
else:
|
||||
it = self.create_tree_iter(data)
|
||||
return (True, it)
|
||||
|
||||
def row_deleted(self, path, node=None):
|
||||
"""Notify the model a row has been deleted.
|
||||
|
||||
Use the node parameter to ensure the user_data reference associated
|
||||
with the path is properly freed by this model.
|
||||
|
||||
:Parameters:
|
||||
path : Gtk.TreePath
|
||||
Path to the row that has been deleted.
|
||||
node : object
|
||||
Python object used as the node returned from "on_get_iter". This is
|
||||
optional but ensures the model will not leak references to this object.
|
||||
"""
|
||||
super(GenericTreeModel, self).row_deleted(path)
|
||||
node_id = id(node)
|
||||
if node_id in self._held_refs:
|
||||
del self._held_refs[node_id]
|
||||
|
||||
#
|
||||
# GtkTreeModel Interface Implementation
|
||||
#
|
||||
@handle_exception(0)
|
||||
def do_get_flags(self):
|
||||
"""Internal method."""
|
||||
return self.on_get_flags()
|
||||
|
||||
@handle_exception(0)
|
||||
def do_get_n_columns(self):
|
||||
"""Internal method."""
|
||||
return self.on_get_n_columns()
|
||||
|
||||
@handle_exception(GObject.TYPE_INVALID)
|
||||
def do_get_column_type(self, index):
|
||||
"""Internal method."""
|
||||
return self.on_get_column_type(index)
|
||||
|
||||
@handle_exception((False, None))
|
||||
def do_get_iter(self, path):
|
||||
"""Internal method."""
|
||||
return self._create_tree_iter(self.on_get_iter(path))
|
||||
|
||||
@handle_exception(False)
|
||||
def do_iter_next(self, iter):
|
||||
"""Internal method."""
|
||||
if iter is None:
|
||||
next_data = self.on_iter_next(None)
|
||||
else:
|
||||
next_data = self.on_iter_next(self.get_user_data(iter))
|
||||
|
||||
self.set_user_data(iter, next_data)
|
||||
return next_data is not None
|
||||
|
||||
@handle_exception(None)
|
||||
def do_get_path(self, iter):
|
||||
"""Internal method."""
|
||||
path = self.on_get_path(self.get_user_data(iter))
|
||||
if path is None:
|
||||
return None
|
||||
else:
|
||||
return Gtk.TreePath(path)
|
||||
|
||||
@handle_exception(None)
|
||||
def do_get_value(self, iter, column):
|
||||
"""Internal method."""
|
||||
return self.on_get_value(self.get_user_data(iter), column)
|
||||
|
||||
@handle_exception((False, None))
|
||||
def do_iter_children(self, parent):
|
||||
"""Internal method."""
|
||||
data = self.get_user_data(parent) if parent else None
|
||||
return self._create_tree_iter(self.on_iter_children(data))
|
||||
|
||||
@handle_exception(False)
|
||||
def do_iter_has_child(self, parent):
|
||||
"""Internal method."""
|
||||
return self.on_iter_has_child(self.get_user_data(parent))
|
||||
|
||||
@handle_exception(0)
|
||||
def do_iter_n_children(self, iter):
|
||||
"""Internal method."""
|
||||
if iter is None:
|
||||
return self.on_iter_n_children(None)
|
||||
return self.on_iter_n_children(self.get_user_data(iter))
|
||||
|
||||
@handle_exception((False, None))
|
||||
def do_iter_nth_child(self, parent, n):
|
||||
"""Internal method."""
|
||||
if parent is None:
|
||||
data = self.on_iter_nth_child(None, n)
|
||||
else:
|
||||
data = self.on_iter_nth_child(self.get_user_data(parent), n)
|
||||
return self._create_tree_iter(data)
|
||||
|
||||
@handle_exception((False, None))
|
||||
def do_iter_parent(self, child):
|
||||
"""Internal method."""
|
||||
return self._create_tree_iter(self.on_iter_parent(self.get_user_data(child)))
|
||||
|
||||
@handle_exception(None)
|
||||
def do_ref_node(self, iter):
|
||||
self.on_ref_node(self.get_user_data(iter))
|
||||
|
||||
@handle_exception(None)
|
||||
def do_unref_node(self, iter):
|
||||
self.on_unref_node(self.get_user_data(iter))
|
||||
|
||||
#
|
||||
# Python Subclass Overridables
|
||||
#
|
||||
def on_get_flags(self):
|
||||
"""Overridable.
|
||||
|
||||
:Returns Gtk.TreeModelFlags:
|
||||
The flags for this model. See: Gtk.TreeModelFlags
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def on_get_n_columns(self):
|
||||
"""Overridable.
|
||||
|
||||
:Returns:
|
||||
The number of columns for this model.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def on_get_column_type(self, index):
|
||||
"""Overridable.
|
||||
|
||||
:Returns:
|
||||
The column type for the given index.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def on_get_iter(self, path):
|
||||
"""Overridable.
|
||||
|
||||
:Returns:
|
||||
A python object (node) for the given TreePath.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def on_iter_next(self, node):
|
||||
"""Overridable.
|
||||
|
||||
:Parameters:
|
||||
node : object
|
||||
Node at current level.
|
||||
|
||||
:Returns:
|
||||
A python object (node) following the given node at the current level.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def on_get_path(self, node):
|
||||
"""Overridable.
|
||||
|
||||
:Returns:
|
||||
A TreePath for the given node.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def on_get_value(self, node, column):
|
||||
"""Overridable.
|
||||
|
||||
:Parameters:
|
||||
node : object
|
||||
column : int
|
||||
Column index to get the value from.
|
||||
|
||||
:Returns:
|
||||
The value of the column for the given node."""
|
||||
raise NotImplementedError
|
||||
|
||||
def on_iter_children(self, parent):
|
||||
"""Overridable.
|
||||
|
||||
:Returns:
|
||||
The first child of parent or None if parent has no children.
|
||||
If parent is None, return the first node of the model.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def on_iter_has_child(self, node):
|
||||
"""Overridable.
|
||||
|
||||
:Returns:
|
||||
True if the given node has children.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def on_iter_n_children(self, node):
|
||||
"""Overridable.
|
||||
|
||||
:Returns:
|
||||
The number of children for the given node. If node is None,
|
||||
return the number of top level nodes.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def on_iter_nth_child(self, parent, n):
|
||||
"""Overridable.
|
||||
|
||||
:Parameters:
|
||||
parent : object
|
||||
n : int
|
||||
Index of child within parent.
|
||||
|
||||
:Returns:
|
||||
The child for the given parent index starting at 0. If parent None,
|
||||
return the top level node corresponding to "n".
|
||||
If "n" is larger then available nodes, return None.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def on_iter_parent(self, child):
|
||||
"""Overridable.
|
||||
|
||||
:Returns:
|
||||
The parent node of child or None if child is a top level node."""
|
||||
raise NotImplementedError
|
||||
|
||||
def on_ref_node(self, node):
|
||||
pass
|
||||
|
||||
def on_unref_node(self, node):
|
||||
pass
|
||||
@@ -0,0 +1,333 @@
|
||||
# -*- coding: utf-8; mode: python; -*-
|
||||
#
|
||||
# GStreamer Development Utilities
|
||||
#
|
||||
# Copyright (C) 2007 René Stadler <mail@renestadler.de>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""GStreamer Development Utilities Common utils module."""
|
||||
|
||||
import os
|
||||
import logging
|
||||
import subprocess as _subprocess
|
||||
|
||||
|
||||
class SingletonMeta (type):
|
||||
|
||||
def __init__(cls, name, bases, dict_):
|
||||
|
||||
from weakref import WeakValueDictionary
|
||||
|
||||
super(SingletonMeta, cls).__init__(name, bases, dict_)
|
||||
|
||||
cls._singleton_instances = WeakValueDictionary()
|
||||
|
||||
def __call__(cls, *a, **kw):
|
||||
|
||||
kw_key = tuple(sorted(kw.items()))
|
||||
|
||||
try:
|
||||
obj = cls._singleton_instances[a + kw_key]
|
||||
except KeyError:
|
||||
obj = super(SingletonMeta, cls).__call__(*a, **kw)
|
||||
cls._singleton_instances[a + kw_key] = obj
|
||||
return obj
|
||||
|
||||
|
||||
def gettext_cache():
|
||||
"""Return a callable object that operates like gettext.gettext, but is much
|
||||
faster when a string is looked up more than once. This is very useful in
|
||||
loops, where calling gettext.gettext can quickly become a major performance
|
||||
bottleneck."""
|
||||
|
||||
from gettext import gettext
|
||||
|
||||
d = {}
|
||||
|
||||
def gettext_cache_access(s):
|
||||
|
||||
if s not in d:
|
||||
d[s] = gettext(s)
|
||||
return d[s]
|
||||
|
||||
return gettext_cache_access
|
||||
|
||||
|
||||
class ClassProperty (property):
|
||||
|
||||
"Like the property class, but also invokes the getter for class access."
|
||||
|
||||
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
|
||||
|
||||
property.__init__(self, fget, fset, fdel, doc)
|
||||
|
||||
self.__fget = fget
|
||||
|
||||
def __get__(self, obj, obj_class=None):
|
||||
|
||||
ret = property.__get__(self, obj, obj_class)
|
||||
if ret == self:
|
||||
return self.__fget(None)
|
||||
else:
|
||||
return ret
|
||||
|
||||
|
||||
class _XDGClass (object):
|
||||
|
||||
"""Partial implementation of the XDG Base Directory specification v0.6.
|
||||
|
||||
http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html"""
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self._add_base_dir("DATA_HOME", "~/.local/share")
|
||||
self._add_base_dir("CONFIG_HOME", "~/.config")
|
||||
self._add_base_dir("CACHE_HOME", "~/.cache")
|
||||
|
||||
def _add_base_dir(self, name, default):
|
||||
|
||||
dir = os.environ.get("XDG_%s" % (name,))
|
||||
if not dir:
|
||||
dir = os.path.expanduser(os.path.join(*default.split("/")))
|
||||
|
||||
setattr(self, name, dir)
|
||||
|
||||
|
||||
XDG = _XDGClass()
|
||||
|
||||
|
||||
class SaveWriteFile (object):
|
||||
|
||||
def __init__(self, filename, mode="wt"):
|
||||
|
||||
from tempfile import mkstemp
|
||||
|
||||
self.logger = logging.getLogger("tempfile")
|
||||
|
||||
dir = os.path.dirname(filename)
|
||||
base_name = os.path.basename(filename)
|
||||
temp_prefix = "%s-tmp" % (base_name,)
|
||||
|
||||
if dir:
|
||||
# Destination dir differs from current directory, ensure that it
|
||||
# exists:
|
||||
try:
|
||||
os.makedirs(dir)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
self.clean_stale(dir, temp_prefix)
|
||||
|
||||
fd, temp_name = mkstemp(dir=dir, prefix=temp_prefix)
|
||||
|
||||
self.target_name = filename
|
||||
self.temp_name = temp_name
|
||||
self.real_file = os.fdopen(fd, mode)
|
||||
|
||||
def __enter__(self):
|
||||
|
||||
return self
|
||||
|
||||
def __exit__(self, *exc_args):
|
||||
|
||||
if exc_args == (None, None, None,):
|
||||
self.close()
|
||||
else:
|
||||
self.discard()
|
||||
|
||||
def __del__(self):
|
||||
|
||||
try:
|
||||
self.discard()
|
||||
except AttributeError:
|
||||
# If __init__ failed, self has no real_file attribute.
|
||||
pass
|
||||
|
||||
def __close_real(self):
|
||||
|
||||
if self.real_file:
|
||||
self.real_file.close()
|
||||
self.real_file = None
|
||||
|
||||
def clean_stale(self, dir, temp_prefix):
|
||||
|
||||
from time import time
|
||||
from glob import glob
|
||||
|
||||
now = time()
|
||||
pattern = os.path.join(dir, "%s*" % (temp_prefix,))
|
||||
|
||||
for temp_filename in glob(pattern):
|
||||
mtime = os.stat(temp_filename).st_mtime
|
||||
if now - mtime > 3600:
|
||||
self.logger.info("deleting stale temporary file %s",
|
||||
temp_filename)
|
||||
try:
|
||||
os.unlink(temp_filename)
|
||||
except EnvironmentError as exc:
|
||||
self.logger.warning("deleting stale temporary file "
|
||||
"failed: %s", exc)
|
||||
|
||||
def tell(self, *a, **kw):
|
||||
|
||||
return self.real_file.tell(*a, **kw)
|
||||
|
||||
def write(self, *a, **kw):
|
||||
|
||||
return self.real_file.write(*a, **kw)
|
||||
|
||||
def close(self):
|
||||
|
||||
self.__close_real()
|
||||
|
||||
if self.temp_name:
|
||||
try:
|
||||
os.rename(self.temp_name, self.target_name)
|
||||
except OSError as exc:
|
||||
import errno
|
||||
if exc.errno == errno.EEXIST:
|
||||
# We are probably on windows.
|
||||
os.unlink(self.target_name)
|
||||
os.rename(self.temp_name, self.target_name)
|
||||
self.temp_name = None
|
||||
|
||||
def discard(self):
|
||||
|
||||
self.__close_real()
|
||||
|
||||
if self.temp_name:
|
||||
|
||||
try:
|
||||
os.unlink(self.temp_name)
|
||||
except EnvironmentError as exc:
|
||||
self.logger.warning("deleting temporary file failed: %s", exc)
|
||||
self.temp_name = None
|
||||
|
||||
|
||||
class TeeWriteFile (object):
|
||||
|
||||
# TODO Py2.5: Add context manager methods.
|
||||
|
||||
def __init__(self, *file_objects):
|
||||
|
||||
self.files = list(file_objects)
|
||||
|
||||
def close(self):
|
||||
|
||||
for file in self.files:
|
||||
file.close()
|
||||
|
||||
def flush(self):
|
||||
|
||||
for file in self.files:
|
||||
file.flush()
|
||||
|
||||
def write(self, string):
|
||||
|
||||
for file in self.files:
|
||||
file.write(string)
|
||||
|
||||
def writelines(self, lines):
|
||||
|
||||
for file in self.files:
|
||||
file.writelines(lines)
|
||||
|
||||
|
||||
class FixedPopen (_subprocess.Popen):
|
||||
|
||||
def __init__(self, args, **kw):
|
||||
|
||||
# Unconditionally specify all descriptors as redirected, to
|
||||
# work around Python bug #1358527 (which is triggered for
|
||||
# console-less applications on Windows).
|
||||
|
||||
close = []
|
||||
|
||||
for name in ("stdin", "stdout", "stderr",):
|
||||
target = kw.get(name)
|
||||
if not target:
|
||||
kw[name] = _subprocess.PIPE
|
||||
close.append(name)
|
||||
|
||||
_subprocess.Popen.__init__(self, args, **kw)
|
||||
|
||||
for name in close:
|
||||
fp = getattr(self, name)
|
||||
fp.close()
|
||||
setattr(self, name, None)
|
||||
|
||||
|
||||
class DevhelpError (EnvironmentError):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class DevhelpUnavailableError (DevhelpError):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class DevhelpClient (object):
|
||||
|
||||
def available(self):
|
||||
|
||||
try:
|
||||
self.version()
|
||||
except DevhelpUnavailableError:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def version(self):
|
||||
|
||||
return self._invoke("--version")
|
||||
|
||||
def search(self, entry):
|
||||
|
||||
self._invoke_no_interact("-s", entry)
|
||||
|
||||
def _check_os_error(self, exc):
|
||||
|
||||
import errno
|
||||
if exc.errno == errno.ENOENT:
|
||||
raise DevhelpUnavailableError()
|
||||
|
||||
def _invoke(self, *args):
|
||||
|
||||
from subprocess import PIPE
|
||||
|
||||
try:
|
||||
proc = FixedPopen(("devhelp",) + args,
|
||||
stdout=PIPE)
|
||||
except OSError as exc:
|
||||
self._check_os_error(exc)
|
||||
raise
|
||||
|
||||
out, err = proc.communicate()
|
||||
|
||||
if proc.returncode is not None and proc.returncode != 0:
|
||||
raise DevhelpError("devhelp exited with status %i"
|
||||
% (proc.returncode,))
|
||||
return out
|
||||
|
||||
def _invoke_no_interact(self, *args):
|
||||
|
||||
from subprocess import PIPE
|
||||
|
||||
try:
|
||||
proc = FixedPopen(("devhelp",) + args)
|
||||
except OSError as exc:
|
||||
self._check_os_error(exc)
|
||||
raise
|
||||
Reference in New Issue
Block a user