Async connect/disconnect. Moved everything to a new Async and Timeout which cleans...
[theonering] / src / util / go_utils.py
1 #!/usr/bin/env python
2
3 from __future__ import with_statement
4
5 import time
6 import functools
7 import logging
8
9 import gobject
10
11 import misc
12
13
14 _moduleLogger = logging.getLogger("go_utils")
15
16
17 def make_idler(func):
18         """
19         Decorator that makes a generator-function into a function that will continue execution on next call
20         """
21         a = []
22
23         @functools.wraps(func)
24         def decorated_func(*args, **kwds):
25                 if not a:
26                         a.append(func(*args, **kwds))
27                 try:
28                         a[0].next()
29                         return True
30                 except StopIteration:
31                         del a[:]
32                         return False
33
34         return decorated_func
35
36
37 def async(func):
38         """
39         Make a function mainloop friendly. the function will be called at the
40         next mainloop idle state.
41
42         >>> import misc
43         >>> misc.validate_decorator(async)
44         """
45
46         @functools.wraps(func)
47         def new_function(*args, **kwargs):
48
49                 def async_function():
50                         func(*args, **kwargs)
51                         return False
52
53                 gobject.idle_add(async_function)
54
55         return new_function
56
57
58 class Async(object):
59
60         def __init__(self, func, once = True):
61                 self.__func = func
62                 self.__idleId = None
63                 self.__once = once
64
65         def start(self):
66                 assert self.__idleId is None
67                 if self.__once:
68                         self.__idleId = gobject.idle_add(self._on_once)
69                 else:
70                         self.__idleId = gobject.idle_add(self.__func)
71
72         def cancel(self):
73                 if self.__idleId is not None:
74                         gobject.source_remove(self.__idleId)
75                         self.__idleId = None
76
77         @misc.log_exception(_moduleLogger)
78         def _on_once(self):
79                 self.cancel()
80                 try:
81                         self.__func()
82                 finally:
83                         return False
84
85
86 class Timeout(object):
87
88         def __init__(self, func):
89                 self.__func = func
90                 self.__timeoutId = None
91
92         def start(self, **kwds):
93                 assert self.__timeoutId is None
94
95                 assert len(kwds) == 1
96                 timeoutInSeconds = kwds["seconds"]
97                 assert 0 <= timeoutInSeconds
98                 if timeoutInSeconds == 0:
99                         self.__timeoutId = gobject.idle_add(self._on_once)
100                 else:
101                         timeout_add_seconds(timeoutInSeconds, self._on_once)
102
103         def cancel(self):
104                 if self.__timeoutId is not None:
105                         gobject.source_remove(self.__timeoutId)
106                         self.__timeoutId = None
107
108         @misc.log_exception(_moduleLogger)
109         def _on_once(self):
110                 self.cancel()
111                 try:
112                         self.__func()
113                 finally:
114                         return False
115
116
117 def throttled(minDelay, queue):
118         """
119         Throttle the calls to a function by queueing all the calls that happen
120         before the minimum delay
121
122         >>> import misc
123         >>> import Queue
124         >>> misc.validate_decorator(throttled(0, Queue.Queue()))
125         """
126
127         def actual_decorator(func):
128
129                 lastCallTime = [None]
130
131                 def process_queue():
132                         if 0 < len(queue):
133                                 func, args, kwargs = queue.pop(0)
134                                 lastCallTime[0] = time.time() * 1000
135                                 func(*args, **kwargs)
136                         return False
137
138                 @functools.wraps(func)
139                 def new_function(*args, **kwargs):
140                         now = time.time() * 1000
141                         if (
142                                 lastCallTime[0] is None or
143                                 (now - lastCallTime >= minDelay)
144                         ):
145                                 lastCallTime[0] = now
146                                 func(*args, **kwargs)
147                         else:
148                                 queue.append((func, args, kwargs))
149                                 lastCallDelta = now - lastCallTime[0]
150                                 processQueueTimeout = int(minDelay * len(queue) - lastCallDelta)
151                                 gobject.timeout_add(processQueueTimeout, process_queue)
152
153                 return new_function
154
155         return actual_decorator
156
157
158 def _old_timeout_add_seconds(timeout, callback):
159         return gobject.timeout_add(timeout * 1000, callback)
160
161
162 def _timeout_add_seconds(timeout, callback):
163         return gobject.timeout_add_seconds(timeout, callback)
164
165
166 try:
167         gobject.timeout_add_seconds
168         timeout_add_seconds = _timeout_add_seconds
169 except AttributeError:
170         timeout_add_seconds = _old_timeout_add_seconds