Improvements to GUI extension points handling - added method to remove these from PluginManager (memory optimization).

Removed logging from most of the code.
This commit is contained in:
Mateusz Biliński 2008-08-03 13:29:11 +00:00
parent 5cce0a8ca9
commit 16ac65e58b
12 changed files with 226 additions and 131 deletions

View File

@ -38,11 +38,8 @@ class AcronymsExpanderPlugin(GajimPlugin):
description = u'''Replaces acronyms (or other strings) with given expansions/substitutes.'''
authors = [u'Mateusz Biliński <mateusz@bilinski.it>']
homepage = u'http://blog.bilinski.it'
#@log_calls('AcronymsExpanderPlugin')
#def __init__(self):
#super(AcronymsExpanderPlugin, self).__init__()
@log_calls('AcronymsExpanderPlugin')
def init(self):
self.config_dialog = None
@ -71,20 +68,20 @@ class AcronymsExpanderPlugin(GajimPlugin):
ACRONYMS = self.config['ACRONYMS']
INVOKER = self.config['INVOKER']
t = tb.get_text(tb.get_start_iter(), tb.get_end_iter())
log.debug('%s %d'%(t, len(t)))
#log.debug('%s %d'%(t, len(t)))
if t and t[-1] == INVOKER:
log.debug("changing msg text")
#log.debug("changing msg text")
base,sep,head=t[:-1].rpartition(INVOKER)
log.debug('%s | %s | %s'%(base, sep, head))
#log.debug('%s | %s | %s'%(base, sep, head))
if head in ACRONYMS:
head = ACRONYMS[head]
log.debug("head: %s"%(head))
t = "".join((base, sep, head, INVOKER))
log.debug("turning off notify")
#log.debug("turning off notify")
tb.freeze_notify()
log.debug("setting text: '%s'"%(t))
#log.debug("setting text: '%s'"%(t))
tb.set_text(t)
log.debug("turning on notify")
#log.debug("turning on notify")
tb.thaw_notify()
@log_calls('AcronymsExpanderPlugin')

View File

@ -94,7 +94,6 @@ http://trac.gajim.org/attachment/ticket/4133'''
if self.config['show_banner_resource'] or self.config['banner_small_fonts']:
banner_name_label = chat_control.xml.get_widget('banner_name_label')
label_text = banner_name_label.get_label()
log.debug('label_text = "%s"'%(label_text))
contact = chat_control.contact
jid = contact.jid

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
<!--Generated with glade3 3.4.5 on Tue Jul 29 19:58:18 2008 -->
<!--Generated with glade3 3.4.5 on Sun Aug 3 13:57:25 2008 -->
<glade-interface>
<widget class="GtkWindow" id="window1">
<child>
@ -12,40 +12,58 @@
<property name="column_spacing">7</property>
<property name="row_spacing">5</property>
<child>
<widget class="GtkHBox" id="hbox2">
<widget class="GtkLabel" id="label1">
<property name="visible">True</property>
<child>
<widget class="GtkSpinButton" id="message_length_spinbutton">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip" translatable="yes">Message length at which notification is invoked.</property>
<property name="width_chars">6</property>
<property name="adjustment">0 0 999999 1 10 10</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
<signal name="value_changed" handler="on_message_length_spinbutton_value_changed"/>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkAlignment" id="alignment2">
<property name="visible">True</property>
<child>
<placeholder/>
</child>
</widget>
<packing>
<property name="position">1</property>
</packing>
</child>
<property name="tooltip" translatable="yes">Message length at which notification is invoked.</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Message length:</property>
</widget>
<packing>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="tooltip" translatable="yes">Background color of text entry field in chat window when notification is invoked.</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Notification color:</property>
</widget>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="tooltip" translatable="yes">JabberIDs that plugin should be used with (eg. restrict only to one microblogging bot). Use comma (without space) as separator. If empty plugin is used with every JID. </property>
<property name="xalign">0</property>
<property name="label" translatable="yes">JabberIDs to include:</property>
</widget>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<widget class="GtkEntry" id="jids_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip" translatable="yes">JabberIDs that plugin should be used with (eg. restrict only to one microblogging bot). Use comma (without space) as separator. If empty plugin is used with every JID. </property>
<signal name="changed" handler="on_jids_entry_changed"/>
<signal name="editing_done" handler="on_jids_entry_editing_done"/>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="x_options">GTK_FILL</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
@ -91,57 +109,39 @@
</packing>
</child>
<child>
<widget class="GtkEntry" id="jids_entry">
<widget class="GtkHBox" id="hbox2">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip" translatable="yes">JabberIDs that plugin should be used with (eg. restrict only to one microblogging bot). If empty plugin is used with every JID. [not implemented]</property>
<signal name="changed" handler="on_jids_entry_changed"/>
<signal name="editing_done" handler="on_jids_entry_editing_done"/>
<child>
<widget class="GtkSpinButton" id="message_length_spinbutton">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip" translatable="yes">Message length at which notification is invoked.</property>
<property name="width_chars">6</property>
<property name="adjustment">0 0 999999 1 10 10</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
<signal name="value_changed" handler="on_message_length_spinbutton_value_changed"/>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkAlignment" id="alignment2">
<property name="visible">True</property>
<child>
<placeholder/>
</child>
</widget>
<packing>
<property name="position">1</property>
</packing>
</child>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="tooltip" translatable="yes">JabberIDs that plugin should be used with (eg. restrict only to one microblogging bot). If empty plugin is used with every JID. [not implemented]</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">JabberIDs to include:</property>
</widget>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="tooltip" translatable="yes">Background color of text entry field in chat window when notification is invoked.</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Notification color:</property>
</widget>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="tooltip" translatable="yes">Message length at which notification is invoked.</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Message length:</property>
</widget>
<packing>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>

View File

@ -40,10 +40,6 @@ class LengthNotifierPlugin(GajimPlugin):
description = u'''Highlights message entry field in chat window when given length of message is exceeded.'''
authors = [u'Mateusz Biliński <mateusz@bilinski.it>']
homepage = u'http://blog.bilinski.it'
#@log_calls('LengthNotifierPlugin')
#def __init__(self):
#super(LengthNotifierPlugin, self).__init__()
@log_calls('LengthNotifierPlugin')
def init(self):
@ -107,7 +103,8 @@ class LengthNotifierPlugin(GajimPlugin):
if d['prev_color']:
tv.modify_base(gtk.STATE_NORMAL, d['prev_color'])
except AttributeError, error:
log.debug('Length Notifier Plugin was (probably) never connected with this chat window.\n Error: %s' % (error))
pass
#log.debug('Length Notifier Plugin was (probably) never connected with this chat window.\n Error: %s' % (error))
@log_calls('LengthNotifierPlugin')
def jid_is_ok(self, jid):

View File

@ -40,14 +40,9 @@ class RosterButtonsPlugin(GajimPlugin):
description = u'''Adds quick action buttons to roster window.'''
authors = [u'Mateusz Biliński <mateusz@bilinski.it>']
homepage = u'http://blog.bilinski.it'
#@log_calls('RosterButtonsPlugin')
#def __init__(self):
#super(RosterButtonsPlugin, self).__init__()
@log_calls('RosterButtonsPlugin')
def init(self):
#log.debug('self.__path__==%s'%(self.__path__))
self.GLADE_FILE_PATH = self.local_file_path('roster_buttons.glade')
self.roster_vbox = gajim.interface.roster.xml.get_widget('roster_vbox2')

View File

@ -294,7 +294,15 @@ class ChatControlBase(MessageControl):
self.smooth = True
# PluginSystem: adding GUI extension point for ChatControlBase
# instance object (also subclasses, eg. ChatControl or GroupchatControl)
gajim.plugin_manager.gui_extension_point('chat_control_base', self)
def shutdown(self):
# PluginSystem: removing GUI extension points connected with ChatControlBase
# instance object
gajim.plugin_manager.remove_gui_extension_point('chat_control_base', self)
gajim.plugin_manager.remove_gui_extension_point('chat_control_base_draw_banner', self)
def on_msg_textview_populate_popup(self, textview, menu):
'''we override the default context menu and we prepend an option to switch languages'''
@ -1144,6 +1152,8 @@ class ChatControl(ChatControlBase):
# restore previous conversation
self.restore_conversation()
# PluginSystem: adding GUI extension point for this ChatControl
# instance object
gajim.plugin_manager.gui_extension_point('chat_control', self)
def on_avatar_eventbox_enter_notify_event(self, widget, event):
@ -2022,6 +2032,13 @@ class ChatControl(ChatControlBase):
self.reset_kbd_mouse_timeout_vars()
def shutdown(self):
# PluginSystem: calling shutdown of super class (ChatControlBase) to let it remove
# it's GUI extension points
super(ChatControl, self).shutdown()
# PluginSystem: removing GUI extension points connected with ChatControl
# instance object
gajim.plugin_manager.remove_gui_extension_point('chat_control', self)
# destroy banner tooltip - bug #pygtk for that!
self.status_tooltip.destroy()

View File

@ -1636,6 +1636,11 @@ class GroupchatControl(ChatControlBase):
status = self.subject)
def shutdown(self, status='offline'):
# PluginSystem: calling shutdown of super class (ChatControlBase)
# to let it remove it's GUI extension points
super(GroupchatControl, self).shutdown()
# destroy banner tooltip - bug #pygtk for that!
self.subject_tooltip.destroy()
gajim.connections[self.account].send_gc_status(self.nick, self.room_jid,

View File

@ -28,7 +28,7 @@ TYPE_PM = 'pm'
####################
class MessageControl:
class MessageControl(object):
'''An abstract base widget that can embed in the gtk.Notebook of a MessageWindow'''
def __init__(self, type_id, parent_win, widget_name, contact, account, resource = None):

View File

@ -170,7 +170,7 @@ class PluginsWindow(object):
@log_calls('PluginsWindow')
def on_configure_plugin_button_clicked(self, widget):
log.debug('widget: %s'%(widget))
#log.debug('widget: %s'%(widget))
selection = self.installed_plugins_treeview.get_selection()
model, iter = selection.get_selected()
if iter:

View File

@ -50,7 +50,8 @@ class log_calls(object):
Decorator class for functions to easily log when they are entered and left.
'''
filter_out_classes = ['PluginManager']
filter_out_classes = ['GajimPlugin', 'GajimPluginConfig',
'GajimPluginConfigDialog', 'PluginsWindow']
'''
List of classes from which no logs should be emited when methods are called,
eventhough `log_calls` decorator is used.
@ -129,10 +130,11 @@ class Singleton(type):
def __call__(cls,*args,**kw):
if cls.instance is None:
cls.instance=super(Singleton,cls).__call__(*args,**kw)
log.debug('%(classname)s - new instance created'%{
'classname' : cls.__name__})
#log.debug('%(classname)s - new instance created'%{
#'classname' : cls.__name__})
else:
log.debug('%(classname)s - returning already existing instance'%{
'classname' : cls.__name__})
pass
#log.debug('%(classname)s - returning already existing instance'%{
#'classname' : cls.__name__})
return cls.instance

View File

@ -130,10 +130,22 @@ class GajimPlugin(object):
def save_config(self):
self.config.save()
@log_calls('GajimPlugin')
@log_calls('GajimPlugin')
def load_config(self):
self.config.load()
def __eq__(self, plugin):
if self.short_name == plugin.short_name:
return True
return False
def __ne__(self, plugin):
if self.short_name != plugin.short_name:
return True
return False
@log_calls('GajimPlugin')
def local_file_path(self, file_name):
return os.path.join(self.__path__, file_name)

View File

@ -96,11 +96,11 @@ class PluginManager(object):
for path in gajim.PLUGINS_DIRS:
self.add_plugins(PluginManager.scan_dir_for_plugins(path))
log.debug('plugins: %s'%(self.plugins))
#log.debug('plugins: %s'%(self.plugins))
self._activate_all_plugins_from_global_config()
log.debug('active: %s'%(self.active_plugins))
#log.debug('active: %s'%(self.active_plugins))
@log_calls('PluginManager')
def _plugin_has_entry_in_global_config(self, plugin):
@ -120,11 +120,16 @@ class PluginManager(object):
and adding class from reloaded module or ignoring adding plug-in?
'''
plugin = plugin_class()
if not self._plugin_has_entry_in_global_config(plugin):
self._create_plugin_entry_in_global_config(plugin)
self.plugins.append(plugin)
plugin.active = False
if plugin not in self.plugins:
if not self._plugin_has_entry_in_global_config(plugin):
self._create_plugin_entry_in_global_config(plugin)
self.plugins.append(plugin)
plugin.active = False
else:
log.info('Not loading plugin %s v%s from module %s (identified by short name: %s). Plugin already loaded.'%(
plugin.name, plugin.version, plugin.__module__, plugin.short_name))
@log_calls('PluginManager')
def add_plugins(self, plugin_classes):
@ -134,7 +139,9 @@ class PluginManager(object):
@log_calls('PluginManager')
def gui_extension_point(self, gui_extpoint_name, *args):
'''
Invokes all handlers (from plugins) for particular GUI extension point.
Invokes all handlers (from plugins) for particular GUI extension point
and adds it to collection for further processing (eg. by plugins not active
yet).
:param gui_extpoint_name: name of GUI extension point.
:type gui_extpoint_name: unicode
@ -157,10 +164,69 @@ class PluginManager(object):
self._add_gui_extension_point_call_to_list(gui_extpoint_name, *args)
self._execute_all_handlers_of_gui_extension_point(gui_extpoint_name, *args)
@log_calls('PluginManager')
def remove_gui_extension_point(self, gui_extpoint_name, *args):
'''
Removes GUI extension point from collection held by `PluginManager`.
From this point this particular extension point won't be visible
to plugins (eg. it won't invoke any handlers when plugin is activated).
GUI extension point is removed completely (there is no way to recover it
from inside `PluginManager`).
Removal is needed when instance object that given extension point was
connect with is destroyed (eg. ChatControl is closed or context menu
is hidden).
Each `PluginManager.gui_extension_point` call should have a call of
`PluginManager.remove_gui_extension_point` related to it.
:note: in current implementation different arguments mean different
extension points. The same arguments and the same name mean
the same extension point.
:todo: instead of using argument to identify which extpoint should be
removed, maybe add additional 'id' argument - this would work similar
hash in Python objects. 'id' would be calculated based on arguments
passed or on anything else (even could be constant). This would give
core developers (that add new extpoints) more freedom, but is this
necessary?
:param gui_extpoint_name: name of GUI extension point.
:type gui_extpoint_name: unicode
:param args: arguments that `PluginManager.gui_extension_point` was
called with for this extension point. This is used (along with
extension point name) to identify element to be removed.
:type args: tuple
'''
log.debug('name: %s\n args: %s'%(gui_extpoint_name, args))
@log_calls('PluginManager')
def _add_gui_extension_point_call_to_list(self, gui_extpoint_name, *args):
self.gui_extension_points.setdefault(gui_extpoint_name, []).append(args)
'''
Adds GUI extension point call to list of calls.
This is done only if such call hasn't been added already
(same extension point name and same arguments).
:note: This is assumption that GUI extension points are different only
if they have different name or different arguments.
:param gui_extpoint_name: GUI extension point name used to identify it
by plugins.
:type gui_extpoint_name: str
:param args: parameters to be passed to extension point handlers
(typically and object that invokes `gui_extension_point`; however,
this can be practically anything)
:type args: tuple
'''
if ((gui_extpoint_name not in self.gui_extension_points)
or (args not in self.gui_extension_points[gui_extpoint_name])):
self.gui_extension_points.setdefault(gui_extpoint_name, []).append(args)
@log_calls('PluginManager')
def _execute_all_handlers_of_gui_extension_point(self, gui_extpoint_name, *args):
@ -287,57 +353,62 @@ class PluginManager(object):
#log.debug(sys.path)
for elem_name in dir_list:
log.debug('- "%s"'%(elem_name))
#log.debug('- "%s"'%(elem_name))
file_path = os.path.join(path, elem_name)
log.debug(' "%s"'%(file_path))
#log.debug(' "%s"'%(file_path))
module = None
if os.path.isfile(file_path) and fnmatch.fnmatch(file_path,'*.py'):
module_name = os.path.splitext(elem_name)[0]
log.debug('Possible module detected.')
#log.debug('Possible module detected.')
try:
module = __import__(module_name)
log.debug('Module imported.')
#log.debug('Module imported.')
except ValueError, value_error:
log.debug('Module not imported successfully. ValueError: %s'%(value_error))
pass
#log.debug('Module not imported successfully. ValueError: %s'%(value_error))
except ImportError, import_error:
log.debug('Module not imported successfully. ImportError: %s'%(import_error))
pass
#log.debug('Module not imported successfully. ImportError: %s'%(import_error))
elif os.path.isdir(file_path):
module_name = elem_name
file_path += os.path.sep
log.debug('Possible package detected.')
#log.debug('Possible package detected.')
try:
module = __import__(module_name)
log.debug('Package imported.')
#log.debug('Package imported.')
except ValueError, value_error:
log.debug('Package not imported successfully. ValueError: %s'%(value_error))
pass
#log.debug('Package not imported successfully. ValueError: %s'%(value_error))
except ImportError, import_error:
log.debug('Package not imported successfully. ImportError: %s'%(import_error))
pass
#log.debug('Package not imported successfully. ImportError: %s'%(import_error))
if module:
log.debug('Attributes processing started')
#log.debug('Attributes processing started')
for module_attr_name in [attr_name for attr_name in dir(module)
if not (attr_name.startswith('__') or
attr_name.endswith('__'))]:
module_attr = getattr(module, module_attr_name)
log.debug('%s : %s'%(module_attr_name, module_attr))
#log.debug('%s : %s'%(module_attr_name, module_attr))
try:
if issubclass(module_attr, GajimPlugin) and \
not module_attr is GajimPlugin:
log.debug('is subclass of GajimPlugin')
#log.debug('is subclass of GajimPlugin')
#log.debug('file_path: %s\nabspath: %s\ndirname: %s'%(file_path, os.path.abspath(file_path), os.path.dirname(os.path.abspath(file_path))))
#log.debug('file_path: %s\ndirname: %s\nabspath: %s'%(file_path, os.path.dirname(file_path), os.path.abspath(os.path.dirname(file_path))))
module_attr.__path__ = os.path.abspath(os.path.dirname(file_path))
plugins_found.append(module_attr)
except TypeError, type_error:
log.debug('module_attr: %s, error : %s'%(
module_name+'.'+module_attr_name,
type_error))
pass
#log.debug('module_attr: %s, error : %s'%(
#module_name+'.'+module_attr_name,
#type_error))
log.debug(module)
#log.debug(module)
return plugins_found