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

« back to all changes in this revision

Viewing changes to _zeitgeist/engine/notify.py

  • Committer: Package Import Robot
  • Author(s): Didier Roche
  • Date: 2011-11-15 11:15:56 UTC
  • mto: (6.2.2 experimental) (1.3.1)
  • mto: This revision was merged to the branch mainline in revision 19.
  • Revision ID: package-import@ubuntu.com-20111115111556-so7cmhfbqongw7hf
Tags: upstream-0.8.99~alpha1
Import upstream version 0.8.99~alpha1

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -.- coding: utf-8 -.-
2
 
 
3
 
# Zeitgeist
4
 
#
5
 
# Copyright © 2009 Mikkel Kamstrup Erlandsen <mikkel.kamstrup@gmail.com>
6
 
#
7
 
# This program is free software: you can redistribute it and/or modify
8
 
# it under the terms of the GNU Lesser General Public License as published by
9
 
# the Free Software Foundation, either version 2.1 of the License, or
10
 
# (at your option) any later version.
11
 
#
12
 
# This program is distributed in the hope that it will be useful,
13
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 
# GNU Lesser General Public License for more details.
16
 
#
17
 
# You should have received a copy of the GNU Lesser General Public License
18
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
 
 
20
 
import dbus
21
 
import logging
22
 
 
23
 
from zeitgeist.datamodel import TimeRange
24
 
 
25
 
log = logging.getLogger("zeitgeist.notify")
26
 
 
27
 
class _MonitorProxy (dbus.Interface):
28
 
        """
29
 
        Connection to a org.gnome.zeitgeist.Monitor interface running on some
30
 
        client to the Zeitgeist engine.
31
 
        """
32
 
        
33
 
        def __init__ (self, owner, monitor_path, time_range, event_templates):
34
 
                """
35
 
                Create a new MonitorProxy for the unique DBus name *owner* on the
36
 
                path *monitor_path*. Note that the path points to an object
37
 
                living under *owner* and not necessarily inside the current
38
 
                process.
39
 
                
40
 
                :param owner: Unique DBus name of the process owning the monitor
41
 
                :param monitor_path: The DBus object path to the monitor object
42
 
                        in the client process
43
 
                :param time_range: a 
44
 
                        :class:`TimeRange <zeitgeist.datamodel.TimeRange` instance
45
 
                        the monitored events must lie within
46
 
                :param event_templates: List of event templates that any events
47
 
                        must match in order to notify this monitor
48
 
                :type event_templates: list of
49
 
                        :class:`Events <zeitgeist.datamodel.Event>`
50
 
                """
51
 
                bus = dbus.SessionBus()
52
 
                proxy = bus.get_object(owner, monitor_path)
53
 
                dbus.Interface.__init__(
54
 
                                self,
55
 
                                proxy,
56
 
                                "org.gnome.zeitgeist.Monitor"
57
 
                )
58
 
                
59
 
                self._owner = owner
60
 
                self._path = monitor_path
61
 
                self._time_range = time_range
62
 
                self._templates = event_templates
63
 
        
64
 
        def __str__ (self):
65
 
                return "%s%s" % (self.owner, self.path)
66
 
        
67
 
        def get_owner (self) : return self._owner
68
 
        owner = property(get_owner, doc="Read only property with the unique DBus name of the process owning the monitor")
69
 
        
70
 
        def get_path (self) : return self._path
71
 
        path = property(get_path, doc="Read only property with the object path of the monitor in the process owning the monitor")
72
 
        
73
 
        def get_time_range (self) : return self._time_range
74
 
        time_range = property(get_time_range, doc="Read only property with matched :class:`TimeRange` of the monitor")
75
 
        
76
 
        def __hash__ (self):
77
 
                return hash(_MonitorProxy.hash(self._owner, self._path))
78
 
        
79
 
        def matches (self, event):
80
 
                """
81
 
                Returns True of this monitor has a template matching *event*
82
 
                
83
 
                :param event: The event to check against the monitor's templates
84
 
                :type event: :class:`Event <zeitgeist.datamodel.Event>`
85
 
                """
86
 
                if not event.in_time_range(self._time_range):
87
 
                        return False
88
 
                
89
 
                if len(self._templates) == 0:
90
 
                        return True
91
 
                
92
 
                for tmpl in self._templates:
93
 
                        if event.matches_template(tmpl) : return True
94
 
                return False
95
 
        
96
 
        def notify_insert (self, time_range, events):
97
 
                """
98
 
                Asynchronously notify the monitor that a collection of matching events has been inserted
99
 
                
100
 
                The events will *not* be filtered through the :meth:`matches`
101
 
                method. It is the responsability of the caller to do that.
102
 
                """
103
 
                for ev in events : ev._make_dbus_sendable()
104
 
                self.NotifyInsert(time_range, events,
105
 
                                        reply_handler=self._notify_reply_handler,
106
 
                                        error_handler=self._notify_error_handler)
107
 
        
108
 
        def notify_delete (self, time_range, event_ids):
109
 
                """
110
 
                Asynchronously notify the monitor that a collection of events has been deleted
111
 
                
112
 
                Only the event ids are passed back to the monitor. Note this
113
 
                method does not check that *time_range* is within the monitor's
114
 
                time range. That is the responsibility of the caller
115
 
                """
116
 
                self.NotifyDelete(time_range, event_ids,
117
 
                                        reply_handler=self._notify_reply_handler,
118
 
                                        error_handler=self._notify_error_handler)
119
 
        
120
 
        @staticmethod
121
 
        def hash(owner, path):
122
 
                """
123
 
                Calculate an integer uniquely identifying the monitor based on
124
 
                the DBus name of the owner and object path of the monitor itself
125
 
                """
126
 
                return hash("%s#%s" % (owner, path))
127
 
        
128
 
        def _notify_reply_handler (self):
129
 
                """
130
 
                Async reply handler for invoking Notify() over DBus
131
 
                """
132
 
                pass
133
 
        
134
 
        def _notify_error_handler (self, error):
135
 
                """
136
 
                Async error handler for invoking Notify() over DBus
137
 
                """
138
 
                log.warn("Failed to deliver notification: %s" % error)
139
 
                
140
 
class MonitorManager:
141
 
        
142
 
        def __init__ (self):
143
 
                self._monitors = {} # hash -> Monitor
144
 
                self._connections = {} # owner -> list of paths
145
 
                
146
 
                # Listen for disconnecting clients to clean up potential dangling monitors
147
 
                dbus.SessionBus().add_signal_receiver (self._name_owner_changed,
148
 
                                                                                           signal_name="NameOwnerChanged",
149
 
                                                                                           dbus_interface=dbus.BUS_DAEMON_IFACE,
150
 
                                                                                           arg2="")
151
 
        
152
 
        def install_monitor (self, owner, monitor_path, time_range, event_templates):
153
 
                """
154
 
                Install a :class:`MonitorProxy` and set it up to receive
155
 
                notifications when events are pushed into the :meth;`dispatch`
156
 
                method.
157
 
                
158
 
                Monitors will automatically be cleaned up if :const:`monitor.owner`
159
 
                disconnects from the bus. To manually remove a monitor call the
160
 
                :meth:`remove_monitor` on this object passing in
161
 
                :const:`monitor.owner` and :const:`monitor.path`.
162
 
                
163
 
                :param owner: Unique DBus name of the process owning the monitor
164
 
                :type owner: string
165
 
                :param monitor_path: The DBus object path for the monitor object
166
 
                        in the client process
167
 
                :type monitor_path: String or :class:`dbus.ObjectPath`
168
 
                :param time_range: a 
169
 
                        :class:`TimeRange <zeitgeist.datamodel.TimeRange` instance
170
 
                        the monitored events must lie within
171
 
                :param event_templates: A list of
172
 
                        :class:`Event <zeitgeist.datamodel.Event>` templates to match
173
 
                :returns: This method has no return value
174
 
                """
175
 
                # Check that we don't already have the monitor, so we don't
176
 
                # needlesly set up a full dbus Proxy
177
 
                monitor_key = _MonitorProxy.hash(owner, monitor_path)
178
 
                if monitor_key in self._monitors:
179
 
                        raise KeyError("Monitor for %s already installed at path %s" % (owner, monitor_path))
180
 
                
181
 
                monitor = _MonitorProxy(owner, monitor_path, time_range, event_templates)
182
 
                self._monitors[monitor_key] = monitor
183
 
                
184
 
                if not monitor.owner in self._connections:
185
 
                        self._connections[owner] = set()
186
 
                
187
 
                self._connections[owner].add(monitor.path)
188
 
        
189
 
        def remove_monitor (self, owner, monitor_path):
190
 
                """
191
 
                Remove an installed monitor.
192
 
                
193
 
                :param owner: Unique DBus name of the process owning the monitor
194
 
                :type owner: string
195
 
                :param monitor_path: The DBus object path for the monitor object
196
 
                        in the client process
197
 
                :type monitor_path: String or :class:`dbus.ObjectPath`
198
 
                """
199
 
                log.debug("Removing monitor %s%s", owner, monitor_path)
200
 
                mon = self._monitors.pop(_MonitorProxy.hash(owner, monitor_path))
201
 
                
202
 
                if not mon:
203
 
                        raise KeyError("Unknown monitor %s for owner %s" % (monitor_path, owner))
204
 
                
205
 
                conn = self._connections[owner]
206
 
                if conn : conn.remove(monitor_path)
207
 
        
208
 
        def notify_insert (self, time_range, events):
209
 
                """
210
 
                Send events to matching monitors.
211
 
                The monitors will only be notified about the events for which
212
 
                they have a matching template, ie. :meth:`MonitorProxy.matches`
213
 
                returns True.
214
 
                
215
 
                :param events: The events to check against the monitor templates
216
 
                :type events: list of :class:`Events <zeitgeist.datamodel.Event>`
217
 
                """
218
 
                for mon in self._monitors.itervalues():
219
 
                        log.debug("Checking monitor %s" % mon)
220
 
                        matching_events = []
221
 
                        
222
 
                        for ev in events:
223
 
                                if mon.matches(ev):
224
 
                                        matching_events.append(ev)
225
 
                        
226
 
                        if matching_events :
227
 
                                log.debug("Notifying %s about %s insertions" % (mon, len(matching_events)))
228
 
                                try:
229
 
                                        mon.notify_insert(time_range.intersect(mon.time_range), matching_events)
230
 
                                except Exception:
231
 
                                        log.exception("Failed to notify monitor: %s" % mon)
232
 
        
233
 
        def notify_delete (self, time_range, event_ids):
234
 
                """
235
 
                Notify monitors with matching time ranges that the events with the given ids have been deleted from the log
236
 
                
237
 
                :param time_range: A TimeRange instance that spans the deleted events
238
 
                :param event_ids: A list of event ids
239
 
                """
240
 
                for mon in self._monitors.itervalues():
241
 
                        log.debug("Checking monitor %s" % mon)
242
 
                        intersecting_range = mon.time_range.intersect(time_range)
243
 
                        
244
 
                        if intersecting_range:
245
 
                                log.debug("Notifying %s about %s deletions" % (mon, len(event_ids)))
246
 
                                try:
247
 
                                        mon.notify_delete(intersecting_range, event_ids)
248
 
                                except Exception:
249
 
                                        log.exception("Failed to notify monitor: %s" % mon)
250
 
                
251
 
        def _name_owner_changed (self, owner, old, new):
252
 
                """
253
 
                Clean up monitors for processes disconnecting from the bus
254
 
                """
255
 
                # Don't proceed if this is a disconnect of an unknown connection
256
 
                if not owner in self._connections :
257
 
                        return
258
 
                
259
 
                conn = self._connections[owner]
260
 
                
261
 
                log.debug("Client disconnected %s" % owner)
262
 
                # we need to copy the conn set here, otherwise self.remove()
263
 
                # will possibly change size of self._connection during iteration
264
 
                # which results in a RuntimeError (thekorn)
265
 
                for path in conn.copy():
266
 
                        self.remove_monitor(owner, path)
267
 
                
268
 
                self._connections.pop(owner)
269