Switching more timeouts to second resolution
[theonering] / src / gtk_toolbox.py
1 #!/usr/bin/python
2
3 from __future__ import with_statement
4
5 import os
6 import errno
7 import time
8 import functools
9 import contextlib
10 import logging
11 import threading
12 import Queue
13
14
15 _moduleLogger = logging.getLogger("gtk_toolbox")
16
17
18 @contextlib.contextmanager
19 def flock(path, timeout=-1):
20         WAIT_FOREVER = -1
21         DELAY = 0.1
22         timeSpent = 0
23
24         acquired = False
25
26         while timeSpent <= timeout or timeout == WAIT_FOREVER:
27                 try:
28                         fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR)
29                         acquired = True
30                         break
31                 except OSError, e:
32                         if e.errno != errno.EEXIST:
33                                 raise
34                 time.sleep(DELAY)
35                 timeSpent += DELAY
36
37         assert acquired, "Failed to grab file-lock %s within timeout %d" % (path, timeout)
38
39         try:
40                 yield fd
41         finally:
42                 os.unlink(path)
43
44
45 def make_idler(func):
46         """
47         Decorator that makes a generator-function into a function that will continue execution on next call
48         """
49         a = []
50
51         @functools.wraps(func)
52         def decorated_func(*args, **kwds):
53                 if not a:
54                         a.append(func(*args, **kwds))
55                 try:
56                         a[0].next()
57                         return True
58                 except StopIteration:
59                         del a[:]
60                         return False
61
62         return decorated_func
63
64
65 def autostart(func):
66         """
67         >>> @autostart
68         ... def grep_sink(pattern):
69         ...     print "Looking for %s" % pattern
70         ...     while True:
71         ...             line = yield
72         ...             if pattern in line:
73         ...                     print line,
74         >>> g = grep_sink("python")
75         Looking for python
76         >>> g.send("Yeah but no but yeah but no")
77         >>> g.send("A series of tubes")
78         >>> g.send("python generators rock!")
79         python generators rock!
80         >>> g.close()
81         """
82
83         @functools.wraps(func)
84         def start(*args, **kwargs):
85                 cr = func(*args, **kwargs)
86                 cr.next()
87                 return cr
88
89         return start
90
91
92 @autostart
93 def printer_sink(format = "%s"):
94         """
95         >>> pr = printer_sink("%r")
96         >>> pr.send("Hello")
97         'Hello'
98         >>> pr.send("5")
99         '5'
100         >>> pr.send(5)
101         5
102         >>> p = printer_sink()
103         >>> p.send("Hello")
104         Hello
105         >>> p.send("World")
106         World
107         >>> # p.throw(RuntimeError, "Goodbye")
108         >>> # p.send("Meh")
109         >>> # p.close()
110         """
111         while True:
112                 item = yield
113                 print format % (item, )
114
115
116 @autostart
117 def null_sink():
118         """
119         Good for uses like with cochain to pick up any slack
120         """
121         while True:
122                 item = yield
123
124
125 @autostart
126 def comap(function, target):
127         """
128         >>> p = printer_sink()
129         >>> cm = comap(lambda x: x+1, p)
130         >>> cm.send((0, ))
131         1
132         >>> cm.send((1.0, ))
133         2.0
134         >>> cm.send((-2, ))
135         -1
136         """
137         while True:
138                 try:
139                         item = yield
140                         mappedItem = function(*item)
141                         target.send(mappedItem)
142                 except Exception, e:
143                         _moduleLogger.exception("Forwarding exception!")
144                         target.throw(e.__class__, str(e))
145
146
147 def _flush_queue(queue):
148         while not queue.empty():
149                 yield queue.get()
150
151
152 @autostart
153 def queue_sink(queue):
154         """
155         >>> q = Queue.Queue()
156         >>> qs = queue_sink(q)
157         >>> qs.send("Hello")
158         >>> qs.send("World")
159         >>> qs.throw(RuntimeError, "Goodbye")
160         >>> qs.send("Meh")
161         >>> qs.close()
162         >>> print [i for i in _flush_queue(q)]
163         [(None, 'Hello'), (None, 'World'), (<type 'exceptions.RuntimeError'>, 'Goodbye'), (None, 'Meh'), (<type 'exceptions.GeneratorExit'>, None)]
164         """
165         while True:
166                 try:
167                         item = yield
168                         queue.put((None, item))
169                 except Exception, e:
170                         queue.put((e.__class__, str(e)))
171                 except GeneratorExit:
172                         queue.put((GeneratorExit, None))
173                         raise
174
175
176 def decode_item(item, target):
177         if item[0] is None:
178                 target.send(item[1])
179                 return False
180         elif item[0] is GeneratorExit:
181                 target.close()
182                 return True
183         else:
184                 target.throw(item[0], item[1])
185                 return False
186
187
188 def nonqueue_source(queue, target):
189         isDone = False
190         while not isDone:
191                 item = queue.get()
192                 isDone = decode_item(item, target)
193                 while not queue.empty():
194                         queue.get_nowait()
195
196
197 def threaded_stage(target, thread_factory = threading.Thread):
198         messages = Queue.Queue()
199
200         run_source = functools.partial(nonqueue_source, messages, target)
201         thread = thread_factory(target=run_source)
202         thread.setDaemon(True)
203         thread.start()
204
205         # Sink running in current thread
206         return queue_sink(messages)
207
208
209 def safecall(f, errorDisplay=None, default=None, exception=Exception):
210         '''
211         Returns modified f. When the modified f is called and throws an
212         exception, the default value is returned
213         '''
214         def _safecall(*args, **argv):
215                 try:
216                         return f(*args, **argv)
217                 except exception, e:
218                         if errorDisplay is not None:
219                                 errorDisplay.push_exception(e)
220                         return default
221         return _safecall
222
223
224 def log_call(logger):
225
226         def log_call_decorator(func):
227
228                 @functools.wraps(func)
229                 def wrapper(*args, **kwds):
230                         _moduleLogger.info("-> %s" % (func.__name__, ))
231                         try:
232                                 return func(*args, **kwds)
233                         finally:
234                                 _moduleLogger.info("<- %s" % (func.__name__, ))
235
236                 return wrapper
237
238         return log_call_decorator
239
240
241 def log_exception(logger):
242
243         def log_exception_decorator(func):
244
245                 @functools.wraps(func)
246                 def wrapper(*args, **kwds):
247                         try:
248                                 return func(*args, **kwds)
249                         except Exception:
250                                 logger.exception(func.__name__)
251                                 raise
252
253                 return wrapper
254
255         return log_exception_decorator
256
257
258 def trace(logger):
259
260         def trace_decorator(func):
261
262                 @functools.wraps(func)
263                 def wrapper(*args, **kwds):
264                         try:
265                                 logger.info("> %s" % (func.__name__, ))
266                                 return func(*args, **kwds)
267                         except Exception:
268                                 logger.exception(func.__name__)
269                                 raise
270                         finally:
271                                 logger.info("< %s" % (func.__name__, ))
272
273                 return wrapper
274
275         return trace_decorator