15
15
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
# The developers of the Exaile media player hereby grant permission
19
# for non-GPL compatible GStreamer and Exaile plugins to be used and
20
# distributed together with GStreamer and Exaile. This permission is
21
# above and beyond the permissions granted by the GPL license by which
22
# Exaile is covered. If you modify this code, you may extend this
23
# exception to your version of the code, but you are not obligated to
24
# do so. If you do not wish to do so, delete this exception statement
18
# The developers of the Exaile media player hereby grant permission
19
# for non-GPL compatible GStreamer and Exaile plugins to be used and
20
# distributed together with GStreamer and Exaile. This permission is
21
# above and beyond the permissions granted by the GPL license by which
22
# Exaile is covered. If you modify this code, you may extend this
23
# exception to your version of the code, but you are not obligated to
24
# do so. If you do not wish to do so, delete this exception statement
25
25
# from your version.
28
28
Provides a signals-like system for sending and listening for 'events'
31
Events are kind of like signals, except they may be listened for on a
32
global scale, rather than connected on a per-object basis like signals
33
are. This means that ANY object can emit ANY event, and these events may
34
be listened for by ANY object. Events may be emitted either syncronously
31
Events are kind of like signals, except they may be listened for on a
32
global scale, rather than connected on a per-object basis like signals
33
are. This means that ANY object can emit ANY event, and these events may
34
be listened for by ANY object. Events may be emitted either syncronously
35
35
or asyncronously, the default is asyncronous.
37
37
The events module also provides an idle_add() function similar to that of
42
42
most appropriate spot is immediately before a return statement.
45
from xl.nls import gettext as _
46
import threading, time, logging, traceback, weakref
47
from new import instancemethod
48
45
from inspect import ismethod
47
from new import instancemethod
49
54
from xl import common
50
55
from xl.nls import gettext as _
53
57
# define these here so the interperter doesn't complain about them
54
58
EVENT_MANAGER = None
109
113
timer = EventTimer(interval, function, *args, **kwargs)
110
114
_TIMERS.append(timer)
114
def log_event(type, object, data, async=True):
118
def timeout_add_seconds(interval, function, *args, **kwargs):
119
timer = EventTimer(interval * 1000, function, *args, **kwargs)
120
_TIMERS.append(timer)
124
def log_event(type, obj, data, async=True):
118
128
type: the 'type' or 'name' of the event. [string]
119
object: the object sending the event. [object]
129
obj: the object sending the event. [object]
120
130
data: some data about the event, None if not required [object]
121
131
async: whether or not to emit asyncronously. [bool]
123
133
global EVENT_MANAGER
124
e = Event(type, object, data, time.time())
134
e = Event(type, obj, data, time.time())
125
135
if async and not _TESTING:
126
136
EVENT_MANAGER.emit_async(e)
128
138
EVENT_MANAGER.emit(e)
130
def add_callback(function, type=None, object=None, *args, **kwargs):
140
def add_callback(function, type=None, obj=None, *args, **kwargs):
132
142
Sets an Event callback
134
You should ALWAYS specify one of the two options on what to listen
135
for. While not forbidden to listen to all events, doing so will
136
cause your callback to be called very frequently, and possibly may
144
You should ALWAYS specify one of the two options on what to listen
145
for. While not forbidden to listen to all events, doing so will
146
cause your callback to be called very frequently, and possibly may
137
147
cause slowness within the player itself.
139
149
@param function: the function to call when the event happens [function]
140
@param type: the 'type' or 'name' of the event to listen for, eg
141
"track_added", "cover_changed". Defaults to any event if
150
@param type: the 'type' or 'name' of the event to listen for, eg
151
"track_added", "cover_changed". Defaults to any event if
142
152
not specified. [string]
143
@param object: the object to listen to events from, eg exaile.collection,
144
exaile.cover_manager. Defaults to any object if not
153
@param obj: the object to listen to events from, eg exaile.collection,
154
exaile.cover_manager. Defaults to any object if not
145
155
specified. [object]
146
156
Any additional paramaters will be passed to the callback.
148
158
global EVENT_MANAGER
149
EVENT_MANAGER.add_callback(function, type, object, args, kwargs)
159
EVENT_MANAGER.add_callback(function, type, obj, args, kwargs)
151
def remove_callback(function, type=None, object=None):
161
def remove_callback(function, type=None, obj=None):
153
163
Removes a callback
158
168
global EVENT_MANAGER
159
EVENT_MANAGER.remove_callback(function, type, object)
169
EVENT_MANAGER.remove_callback(function, type, obj)
161
171
def idle_add(func, *args):
163
173
Adds a function to run when there is spare processor time.
165
175
func: the function to call [function]
167
any additional arguments to idle_add will be passed on to the
177
any additional arguments to idle_add will be passed on to the
170
180
do not use for long-running tasks, so as to avoid blocking other
199
209
Represents an Event
201
def __init__(self, type, object, data, time):
211
def __init__(self, type, obj, data, time):
203
213
type: the 'type' or 'name' for this Event [string]
204
object: the object emitting the Event [object]
214
obj: the object emitting the Event [object]
205
215
data: some piece of data relevant to the Event [object]
228
238
self.valid = False
230
240
class _WeakMethod:
231
"""Represent a weak bound method, i.e. a method doesn't keep alive the
232
object that it is bound to. It uses WeakRef which, used on its own,
233
produces weak methods that are dead on creation, not very useful.
241
"""Represent a weak bound method, i.e. a method doesn't keep alive the
242
object that it is bound to. It uses WeakRef which, used on its own,
243
produces weak methods that are dead on creation, not very useful.
234
244
Typically, you will use the getRef() function instead of using
235
245
this class directly. """
237
247
def __init__(self, method, notifyDead = None):
239
The method must be bound. notifyDead will be called when
249
The method must be bound. notifyDead will be called when
240
250
object that method is bound to dies.
242
252
assert ismethod(method)
248
258
self.objRef = weakref.ref(method.im_self, notifyDead)
249
259
self.fun = method.im_func
250
260
self.cls = method.im_class
252
262
def __call__(self):
253
263
if self.objRef() is None:
256
266
return instancemethod(self.fun, self.objRef(), self.cls)
258
268
def __eq__(self, method2):
259
269
if not isinstance(method2, _WeakMethod):
261
271
return self.fun is method2.fun \
262
272
and self.objRef() is method2.objRef() \
263
273
and self.objRef() is not None
265
275
def __hash__(self):
266
276
return hash(self.fun)
268
278
def __repr__(self):
270
if self.objRef() is None:
280
if self.objRef() is None:
272
282
obj = '<%s at %s%s>' % (self.__class__, id(self), dead)
275
285
def refs(self, weakRef):
276
286
"""Return true if we are storing same object referred to by weakRef."""
277
287
return self.objRef == weakRef
330
340
# This is quite simple. If we have a job, wake up and run it.
331
341
# If we run out of jobs, sleep until we have another one to do.
333
if self._stopped: return
343
if self._stopped: return
334
344
while len(self.queue) == 0:
335
345
self.event.wait()
336
346
self.event.clear()
337
347
func, args = self.queue[0]
338
348
self.queue = self.queue[1:]
340
if self._stopped: return
350
if self._stopped: return
341
351
self._call_function(func, args)
343
353
def events_pending(self):
345
Returns true if there are events pending in the event queue.
355
Returns true if there are events pending in the event queue.
347
357
if len(self.queue) > 0:
352
362
def event_iteration(self):
354
364
Forces an event from the event queue to be processed.
358
368
def wait_for_pending_events(self):
360
370
Blocks until the event queue is empty.
362
372
while self.events_pending():
412
422
if not self.logger_filter or re.search(self.logger_filter,
414
424
logger.debug("Sent '%(type)s' event from "
415
"'%(object)s' with data '%(data)s'." %
416
{'type' : event.type, 'object' : repr(event.object),
425
"'%(object)s' with data '%(data)s'." %
426
{'type' : event.type, 'object' : repr(event.object),
417
427
'data' : repr(event.data)})
437
447
"to %(event)s." % {
438
448
'function': cb.wfunction(),
439
449
'event': event.type})
440
cb.wfunction().__call__(event.type, event.object,
450
cb.wfunction().__call__(event.type, event.object,
441
451
event.data, *cb.args, **cb.kwargs)
443
453
traceback.print_exc()
444
454
# something went wrong inside the function we're calling
446
common.log_exception(logger,
456
common.log_exception(logger,
447
457
message="Event callback exception caught!")
449
459
traceback.print_exc()
452
462
self.lock.release()
454
464
def emit_async(self, event):
458
468
self.idle.add(self.emit, event)
460
def add_callback(self, function, type, object, args, kwargs):
470
def add_callback(self, function, type, obj, args, kwargs):
462
472
Registers a callback.
463
473
You should always specify at least one of type or object.
465
475
@param function: The function to call [function]
466
@param type: The 'type' or 'name' of event to listen for. Defaults
476
@param type: The 'type' or 'name' of event to listen for. Defaults
468
@param object: The object to listen to events from. Defaults
478
@param obj: The object to listen to events from. Defaults
471
481
# add the specified categories if needed.
472
482
if not self.callbacks.has_key(type):
473
483
self.callbacks[type] = weakref.WeakKeyDictionary()
476
if not self.callbacks[type].has_key(object):
477
self.callbacks[type][object] = []
487
callbacks = self.callbacks[type][obj]
489
callbacks = self.callbacks[type][obj] = []
479
491
# add the actual callback
480
self.callbacks[type][object].append(
481
Callback(function, time.time(), args, kwargs))
492
callbacks.append(Callback(function, time.time(), args, kwargs))
483
494
if self.use_logger:
484
495
if not self.logger_filter or re.search(self.logger_filter, type):
485
logger.debug("Added callback %s for [%s, %s]" %
486
(function, type, object))
488
def remove_callback(self, function, type=None, object=None):
496
logger.debug("Added callback %s for [%s, %s]" %
497
(function, type, obj))
499
def remove_callback(self, function, type=None, obj=None):
490
501
Unsets a callback
492
503
The parameters must match those given when the callback was
493
504
registered. (minus any additional args)
495
self.idle.add(self._remove_callback, function, type, object)
506
self.idle.add(self._remove_callback, function, type, obj)
497
def _remove_callback(self, function, type, object):
508
def _remove_callback(self, function, type, obj):
501
512
The parameters must match those given when the callback was
508
for cb in self.callbacks[type][object]:
519
callbacks = self.callbacks[type][obj]
509
521
if cb.wfunction() == function:
510
522
remove.append(cb)