~ubuntu-branches/ubuntu/quantal/zeitgeist/quantal

« back to all changes in this revision

Viewing changes to _zeitgeist/engine/extension.py

* New upstream release. Some of the changes are:
   - Various performance improvements (speed, reduced I/O, etc).
   - Enhancements to the extensions system (eg. feature to ask which
     extensions are active).
   - Various bug fixes (eg. fixed find_event_for_template Python API method).
   - Added new mimetype mappings.
* Updated debian/copyright and debian/zeitgeist-core.install.

Show diffs side-by-side

added added

removed removed

Lines of Context:
7
7
#
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.
12
12
#
13
13
# This program is distributed in the hope that it will be useful,
22
22
import logging
23
23
import weakref # avoid circular references as they confuse garbage collection
24
24
 
25
 
logging.basicConfig(level=logging.DEBUG)
26
25
log = logging.getLogger("zeitgeist.extension")
27
26
 
28
27
import zeitgeist
 
28
from _zeitgeist.engine import constants
29
29
 
30
30
def safe_issubclass(obj, cls):
31
31
        try:
32
32
                return issubclass(obj, cls)
33
33
        except TypeError:
34
34
                return False
 
35
                
 
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.
 
39
        """
 
40
        if safe_issubclass(extension, Extension):
 
41
                return extension.__name__
 
42
        elif isinstance(extension, Extension):
 
43
                return extension.__class__.__name__
 
44
        else:
 
45
                raise TypeError(
 
46
                        "Can't identify %r, an extension has to be a subclass or "
 
47
                        "instance of extension.Extension" %extension)
35
48
 
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.
46
59
        """
47
 
        PUBLIC_METHODS = None
 
60
        PUBLIC_METHODS = []
48
61
        
49
62
        def __init__(self, engine):
50
63
                self.engine = weakref.proxy(engine)
51
64
        
52
 
        def insert_event_hook(self, event, sender):
 
65
        def unload(self):
 
66
                """
 
67
                This method gets called before Zeitgeist stops.
 
68
                
 
69
                Execution of this method isn't guaranteed, and you shouldn't do
 
70
                anything slow in there.
 
71
                """
 
72
                pass
 
73
        
 
74
        def pre_insert_event(self, event, sender):
53
75
                """
54
76
                Hook applied to all events before they are inserted into the
55
77
                log. The returned event is progressively passed through all
69
91
                """
70
92
                return event
71
93
        
72
 
        def get_event_hook(self, event, sender):
 
94
        def post_insert_event(self, event, sender):
 
95
                """
 
96
                Hook applied to all events after they are inserted into the
 
97
                log.
 
98
                
 
99
                :param event: An :class:`Event <zeitgeist.datamodel.Event>`
 
100
                        instance
 
101
                :param sender: The D-Bus bus name of the client
 
102
                :returns: Nothing
 
103
                """
 
104
                pass
 
105
        
 
106
        def get_event(self, event, sender):
73
107
                """
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
84
118
                event as is.
85
119
                
86
120
                :param event: An :class:`Event <zeitgeist.datamodel.Event>`
87
 
                        instance
 
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
90
124
                        should see it
91
125
                """
92
126
                return event
 
127
        
 
128
        def post_delete_events(self, ids, sender):
 
129
                """
 
130
                Hook applied after events have been deleted from the log.
 
131
                
 
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
 
134
                :returns: Nothing
 
135
                """
 
136
                pass
 
137
        
 
138
        def pre_delete_events(self, ids, sender):
 
139
                """
 
140
                Hook applied before events are deleted from the log.
 
141
                
 
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
 
145
                """
 
146
                return ids
93
147
 
94
148
 
95
149
def get_extensions():
118
172
        else:
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
123
177
 
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              
129
182
        extensions = []
130
 
        modules = filter(lambda m : m.endswith(".py"), os.listdir(config.extensiondir)) 
131
 
        modules = map(lambda m : m.rpartition(".")[0], modules)
132
 
        for mod in modules:
133
 
                _zg = __import__("_zeitgeist.engine.extensions." + mod)
134
 
                ext = getattr(_zg.engine.extensions, mod)
 
183
        
 
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)
 
188
        
 
189
        # Find user extensions
 
190
        log.debug("Searching for user extensions in: %s" % constants.USER_EXTENSION_PATH)
 
191
        user_modules = []
 
192
        try:
 
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)
 
195
        except OSError:
 
196
                pass # USER_EXTENSION_PATH doesn't exist
 
197
        
 
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)
 
207
        
 
208
        # Now load the modules already!
 
209
        for mod in user_modules + sys_modules:
 
210
                path, dot, name = mod.rpartition(".")
 
211
                if path:
 
212
                        ext = __import__(mod, globals(), locals(), [name])
 
213
                else:
 
214
                        ext = __import__(name)
 
215
                                
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
140
 
                        
141
221
 
142
222
def _load_class(path):
143
223
        """
176
256
                return "%s(%r)" %(self.__class__.__name__, sorted(self.__methods.keys()))
177
257
                        
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)
 
264
                try:
 
265
                        obj = extension(self.__engine)
 
266
                except Exception:
 
267
                        log.exception("Failed loading the '%s' extension" %ext_name)
 
268
                        return False
 
269
 
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
190
273
                
191
274
        def unload(self, extension=None):
192
275
                """
200
283
                        
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):
 
287
                                self.unload(ext)
205
288
                else:
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__
212
 
                        else:
213
 
                                raise TypeError
 
289
                        ext_name = get_extension_name(extension)
 
290
                        log.debug("Unloading extension '%s'" % ext_name)
214
291
                        obj = self.__extensions[ext_name]
 
292
                        obj.unload()
215
293
                        for method in obj.PUBLIC_METHODS:
216
294
                                del self.methods[method]
217
295
                        del self.__extensions[ext_name]
223
301
                
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
231
309
                                continue
232
310
                return event
233
311
        
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
 
314
        
 
315
                # FIXME: We need a stable iteration order
 
316
                for ext in self.__extensions.itervalues():
 
317
                        event = ext.post_delete_events(ids, sender)
 
318
                        
 
319
        def apply_pre_delete(self, ids, sender):
 
320
                # Apply extension filters if we have an event
 
321
        
 
322
                # FIXME: We need a stable iteration order
 
323
                for ext in self.__extensions.itervalues():
 
324
                        ids = ext.pre_delete_events(ids, sender)
 
325
                        
 
326
                return ids
 
327
        
 
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
240
334
                                return None
241
335
                return event
242
336
        
 
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)
 
341
        
243
342
        def __len__(self):
244
343
                return len(self.__extensions)
245
344
        
262
361
        
263
362
        def __iter__(self):
264
363
                return self.__extensions.itervalues()
 
364
                
 
365
        def iter_names(self):
 
366
                return self.__extensions.iterkeys()