8
8
# This program is free software: you can redistribute it and/or modify
9
9
# it under the terms of the GNU Lesser General Public License as published by
10
# the Free Software Foundation, either version 3 of the License, or
10
# the Free Software Foundation, either version 2.1 of the License, or
11
11
# (at your option) any later version.
13
13
# This program is distributed in the hope that it will be useful,
23
23
import weakref # avoid circular references as they confuse garbage collection
25
logging.basicConfig(level=logging.DEBUG)
26
25
log = logging.getLogger("zeitgeist.extension")
28
from _zeitgeist.engine import constants
30
30
def safe_issubclass(obj, cls):
32
32
return issubclass(obj, cls)
36
def get_extension_name(extension):
37
""" We are using the name of the Extension-class as extension's unique
38
name, later we might want to prepend modul or package names.
40
if safe_issubclass(extension, Extension):
41
return extension.__name__
42
elif isinstance(extension, Extension):
43
return extension.__class__.__name__
46
"Can't identify %r, an extension has to be a subclass or "
47
"instance of extension.Extension" %extension)
36
49
class Extension(object):
37
50
""" Base class for all extensions
44
57
inserted and retrieved from the log. These hooks can either block the
45
58
event completely, modify it, or add additional metadata to it.
49
62
def __init__(self, engine):
50
63
self.engine = weakref.proxy(engine)
52
def insert_event_hook(self, event, sender):
67
This method gets called before Zeitgeist stops.
69
Execution of this method isn't guaranteed, and you shouldn't do
70
anything slow in there.
74
def pre_insert_event(self, event, sender):
54
76
Hook applied to all events before they are inserted into the
55
77
log. The returned event is progressively passed through all
72
def get_event_hook(self, event, sender):
94
def post_insert_event(self, event, sender):
96
Hook applied to all events after they are inserted into the
99
:param event: An :class:`Event <zeitgeist.datamodel.Event>`
101
:param sender: The D-Bus bus name of the client
106
def get_event(self, event, sender):
74
108
Hook applied to all events before they are returned to a client.
75
109
The event returned from this method is progressively passed
86
120
:param event: An :class:`Event <zeitgeist.datamodel.Event>`
121
instance or :const:`None`
88
122
:param sender: The D-Bus bus name of the client
89
123
:returns: The filtered event instance as the client
128
def post_delete_events(self, ids, sender):
130
Hook applied after events have been deleted from the log.
132
:param ids: A list of event ids for the events that has been deleted
133
:param sender: The unique DBus name for the client triggering the delete
138
def pre_delete_events(self, ids, sender):
140
Hook applied before events are deleted from the log.
142
:param ids: A list of event ids for the events requested to be deleted
143
:param sender: The unique DBus name for the client triggering the delete
144
:returns: The filtered list of event ids which should be deleted
95
149
def get_extensions():
119
173
log.debug("No extra extensions")
120
174
extensions = filter(None, extensions)
121
log.debug("daemon is configured to run with these extensions: %r" %extensions)
175
log.debug("Found extensions: %r" %extensions)
122
176
return extensions
124
178
def _scan_extensions():
125
179
"""Look in zeitgeist._config.extensiondir for .py files and return
126
180
a list of classes with all the classes that extends the Extension class"""
127
config = zeitgeist._config
128
log.debug("Searching for extensions in: %s" % config.extensiondir)
181
config = zeitgeist._config
130
modules = filter(lambda m : m.endswith(".py"), os.listdir(config.extensiondir))
131
modules = map(lambda m : m.rpartition(".")[0], modules)
133
_zg = __import__("_zeitgeist.engine.extensions." + mod)
134
ext = getattr(_zg.engine.extensions, mod)
184
# Find system extensions
185
log.debug("Searching for system extensions in: %s" % config.extensiondir)
186
sys_modules = filter(lambda m: m.endswith(".py"), os.listdir(config.extensiondir))
187
sys_modules = map(lambda m: "_zeitgeist.engine.extensions." + m.rpartition(".")[0], sys_modules)
189
# Find user extensions
190
log.debug("Searching for user extensions in: %s" % constants.USER_EXTENSION_PATH)
193
user_modules = filter(lambda m: m.endswith(".py"), os.listdir(os.path.expanduser(constants.USER_EXTENSION_PATH)))
194
user_modules = map(lambda m: m.rpartition(".")[0], user_modules)
196
pass # USER_EXTENSION_PATH doesn't exist
198
# If we have module conflicts let the user extensions win,
199
# and remove the system provided extension from our list
200
user_module_names = map(lambda m: os.path.basename(m), user_modules)
201
for mod in list(sys_modules):
202
mod_name = mod.rpartition(".")[2]
203
if mod_name in user_module_names:
204
log.info ("Extension %s in %s overriding system extension" %
205
(mod_name, constants.USER_EXTENSION_PATH))
206
sys_modules.remove(mod)
208
# Now load the modules already!
209
for mod in user_modules + sys_modules:
210
path, dot, name = mod.rpartition(".")
212
ext = __import__(mod, globals(), locals(), [name])
214
ext = __import__(name)
135
216
for cls in dir(ext):
136
217
cls = getattr(ext, cls)
137
218
if safe_issubclass(cls, Extension) and not cls is Extension:
138
219
extensions.append(cls)
139
220
return extensions
142
222
def _load_class(path):
176
256
return "%s(%r)" %(self.__class__.__name__, sorted(self.__methods.keys()))
178
258
def load(self, extension):
179
log.debug("Loading extension '%s'" % extension.__name__)
180
259
if not issubclass(extension, Extension):
181
260
raise TypeError("Unable to load %r, all extensions must be "
182
261
"subclasses of %r" % (extension, Extension))
183
if getattr(extension, "PUBLIC_METHODS", None) is None:
184
raise ValueError("Unable to load %r, this extension has not "
185
"defined any methods" % extension)
186
obj = extension(self.__engine)
262
ext_name = get_extension_name(extension)
263
log.debug("Loading extension '%s'" %ext_name)
265
obj = extension(self.__engine)
267
log.exception("Failed loading the '%s' extension" %ext_name)
187
270
for method in obj.PUBLIC_METHODS:
188
271
self._register_method(method, getattr(obj, method))
189
self.__extensions[obj.__class__.__name__] = obj
272
self.__extensions[ext_name] = obj
191
274
def unload(self, extension=None):
201
284
# We need to clone the key list to avoid concurrent
202
285
# modification of the extension dict
203
for ext_name in list(self.__extensions.iterkeys()):
204
self.unload(self.__extensions[ext_name])
286
for ext in list(self):
206
log.debug("Unloading extension '%s'" \
207
% extension.__class__.__name__)
208
if safe_issubclass(extension, Extension):
209
ext_name = extension.__name__
210
elif isinstance(extension, Extension):
211
ext_name = extension.__class__.__name__
289
ext_name = get_extension_name(extension)
290
log.debug("Unloading extension '%s'" % ext_name)
214
291
obj = self.__extensions[ext_name]
215
293
for method in obj.PUBLIC_METHODS:
216
294
del self.methods[method]
217
295
del self.__extensions[ext_name]
224
302
# FIXME: We need a stable iteration order
225
303
for ext in self.__extensions.itervalues():
226
event = ext.get_event_hook(event, sender)
304
event = ext.get_event(event, sender)
227
305
if event is None:
228
306
# The event has been blocked by
229
307
# the extension pretend it's
234
def apply_insert_hooks(self, event, sender):
235
# FIXME: We need a stable iteration order
236
for ext in self.__extensions.itervalues():
237
event = ext.insert_event_hook(event, sender)
312
def apply_post_delete(self, ids, sender):
313
# Apply extension filters if we have an event
315
# FIXME: We need a stable iteration order
316
for ext in self.__extensions.itervalues():
317
event = ext.post_delete_events(ids, sender)
319
def apply_pre_delete(self, ids, sender):
320
# Apply extension filters if we have an event
322
# FIXME: We need a stable iteration order
323
for ext in self.__extensions.itervalues():
324
ids = ext.pre_delete_events(ids, sender)
328
def apply_pre_insert(self, event, sender):
329
# FIXME: We need a stable iteration order
330
for ext in self.__extensions.itervalues():
331
event = ext.pre_insert_event(event, sender)
238
332
if event is None:
239
333
# The event has been blocked by the extension
337
def apply_post_insert(self, event, sender):
338
# FIXME: We need a stable iteration order
339
for ext in self.__extensions.itervalues():
340
ext.post_insert_event(event, sender)
243
342
def __len__(self):
244
343
return len(self.__extensions)