Making Async and Timeout callable
[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         def __call__(self):
78                 return self.start()
79
80         @misc.log_exception(_moduleLogger)
81         def _on_once(self):
82                 self.cancel()
83                 try:
84                         self.__func()
85                 finally:
86                         return False
87
88
89 class Timeout(object):
90
91         def __init__(self, func):
92                 self.__func = func
93                 self.__timeoutId = None
94
95         def start(self, **kwds):
96                 assert self.__timeoutId is None
97
98                 assert len(kwds) == 1
99                 timeoutInSeconds = kwds["seconds"]
100                 assert 0 <= timeoutInSeconds
101                 if timeoutInSeconds == 0:
102                         self.__timeoutId = gobject.idle_add(self._on_once)
103                 else:
104                         timeout_add_seconds(timeoutInSeconds, self._on_once)
105
106         def cancel(self):
107                 if self.__timeoutId is not None:
108                         gobject.source_remove(self.__timeoutId)
109                         self.__timeoutId = None
110
111         def __call__(self, **kwds):
112                 return self.start(**kwds)
113
114         @misc.log_exception(_moduleLogger)
115         def _on_once(self):
116                 self.cancel()
117                 try:
118                         self.__func()
119                 finally:
120                         return False
121
122
123 def throttled(minDelay, queue):
124         """
125         Throttle the calls to a function by queueing all the calls that happen
126         before the minimum delay
127
128         >>> import misc
129         >>> import Queue
130         >>> misc.validate_decorator(throttled(0, Queue.Queue()))
131         """
132
133         def actual_decorator(func):
134
135                 lastCallTime = [None]
136
137                 def process_queue():
138                         if 0 < len(queue):
139                                 func, args, kwargs = queue.pop(0)
140                                 lastCallTime[0] = time.time() * 1000
141                                 func(*args, **kwargs)
142                         return False
143
144                 @functools.wraps(func)
145                 def new_function(*args, **kwargs):
146                         now = time.time() * 1000
147                         if (
148                                 lastCallTime[0] is None or
149                                 (now - lastCallTime >= minDelay)
150                         ):
151                                 lastCallTime[0] = now
152                                 func(*args, **kwargs)
153                         else:
154                                 queue.append((func, args, kwargs))
155                                 lastCallDelta = now - lastCallTime[0]
156                                 processQueueTimeout = int(minDelay * len(queue) - lastCallDelta)
157                                 gobject.timeout_add(processQueueTimeout, process_queue)
158
159                 return new_function
160
161         return actual_decorator
162
163
164 def _old_timeout_add_seconds(timeout, callback):
165         return gobject.timeout_add(timeout * 1000, callback)
166
167
168 def _timeout_add_seconds(timeout, callback):
169         return gobject.timeout_add_seconds(timeout, callback)
170
171
172 try:
173         gobject.timeout_add_seconds
174         timeout_add_seconds = _timeout_add_seconds
175 except AttributeError:
176         timeout_add_seconds = _old_timeout_add_seconds