Use list.sort's keyword parameters
[joel/kofoto.git] / src / packages / kofoto / gkofoto / pseudothread.py
1 """This module contains the PseudoThread class."""
2
3 __all__ = ["PseudoThread"]
4
5 import sys
6 if __name__ == "__main__":
7     import pygtk
8     pygtk.require("2.0")
9 import gobject
10
11 class PseudoThread:
12     """A pseudo thread using a GTK idle handler.
13
14     This class provides a simple but powerful emulation of an activity
15     that is run in a separate thread of control. That is, no actual
16     thread is spawned. Instead, the thread-like behaviour is created
17     by letting the GTK main loop call a function when idle. By letting
18     the function be a generator object that yields quite often, the
19     activity can easily remember its state between calls so that
20     long-lived loops can be written without complex state machines.
21
22     There are two ways to specify the activity: by passing a generator
23     object ("the target") to the constructor, or by overriding the
24     _run() method (which must be a generator) in a subclass.
25
26     Once a PseudoThread instance has been created, its activity must
27     be started by calling the start() method. After that, the target's
28     next() method will be called repeatedly as long it returns a true
29     value. If it returns a false value (or throws an exception), the
30     activity is stopped (and will never be started again).
31
32     To get a thread-like behaviour, the activity should not do
33     anything that takes a long time before returning. If the activity
34     needs to sleep, the sleep() method can be called, though.
35
36     Usage examples:
37
38     >>> import pygtk
39     >>> pygtk.require("2.0")
40     >>> import gtk
41     >>> from kofoto.gkofoto.pseudothread import PseudoThread
42     >>> def f(x):
43     ...     for i in range(5):
44     ...         print x, i
45     ...         yield True
46     >>> pt1 = PseudoThread(f("a"))
47     >>> pt1.start()
48     >>> pt2 = PseudoThread(f("b"))
49     >>> pt2.start()
50     >>> gtk.main()
51     a 0
52     b 0
53     a 1
54     b 1
55     a 2
56     b 2
57     a 3
58     b 3
59     a 4
60     b 4
61
62     >>> import pygtk
63     >>> pygtk.require("2.0")
64     >>> import gtk
65     >>> from kofoto.gkofoto.pseudothread import PseudoThread
66     >>> class C(PseudoThread):
67     ...     def _run(self):
68     ...         print "sleeping..."
69     ...         self.sleep(1000)
70     ...         yield True
71     ...         print "done."
72     >>> pt = C()
73     >>> pt.start()
74     >>> gtk.main()
75     sleeping...
76     done.
77     """
78
79     def __init__(self, target=None, priority=gobject.PRIORITY_DEFAULT_IDLE,
80                  error_fp=sys.stderr):
81         """Constructor.
82
83         Arguments:
84
85         target   -- The generator object to be run. If None, the
86                     generator object returned by the _run() method is
87                     run instead.
88         priority -- Priority of the thread; preferably one of the
89                     gobject.PRIORITY_* constants.
90         error_fp -- File object to write tracebacks to.
91         """
92
93         self.__idle_tag = None
94         self.__timeout_tag = None
95         if target is None:
96             target = self._run()
97         self.__target = target
98         self.__error_fp = error_fp
99         self.__priority = priority
100
101     def set_priority(self, priority):
102         """Set priority of the thread.
103
104         The priority is changed immediately, even if the thread is
105         running.
106
107         Arguments:
108
109         priority -- Priority of the thread; preferably one of the
110                     gobject.PRIORITY_* constants.
111         """
112
113         if priority == self.__priority:
114             return
115         self.__priority = priority
116         if self.__idle_tag is not None:
117             self.stop()
118             self.start()
119
120     def sleep(self, ms):
121         """Delay execution of the next iteration of the pseudo thread.
122
123         Arguments:
124
125         ms -- Number of milliseconds to sleep.
126         """
127
128         if self.__timeout_tag is not None:
129             return
130         self.stop()
131         self.__timeout_tag = gobject.timeout_add(ms, self.__timeout_cb)
132
133     def start(self):
134         """Start the pseudo thread.
135
136         It's okay to call this method even if the pseudo thread
137         already is running. Note though that if the pseudo thread has
138         reached the end (i.e. the target has returned false or thrown
139         an exception), the activity will not be restarted.
140         """
141
142         if self.__idle_tag is not None:
143             return
144         self.__idle_tag = gobject.idle_add(
145             self.__idle_cb, priority=self.__priority)
146
147     def stop(self):
148         """Stop the pseudo thread.
149
150         It's okay to call this method even if the pseudo thread
151         is not running.
152         """
153
154         if self.__idle_tag is None:
155             return
156         gobject.source_remove(self.__idle_tag)
157         self.__idle_tag = None
158
159     def is_running(self):
160         """Check whether the pseudo thread is running."""
161
162         return (
163             (self.__idle_tag is not None) or
164             (self.__timeout_tag is not None))
165
166     def __idle_cb(self):
167         try:
168             x = self.__target.next()
169         except StopIteration:
170             x = False
171         except:
172             import traceback
173             traceback.print_exc(file=self.__error_fp)
174             x = False
175         if x:
176             return True
177         else:
178             self.__idle_tag = None
179             return False
180
181     def __timeout_cb(self):
182         self.__timeout_tag = None
183         self.start()
184
185     def _run(self):
186         raise Exception("not overridden")
187
188 ######################################################################
189
190 if __name__ == "__main__":
191     import gtk
192
193     def f(x):
194         for i in range(5):
195             print x, i
196             yield True
197     pt1 = PseudoThread(f("a"))
198     pt1.start()
199     pt2 = PseudoThread(f("b"))
200     pt2.start()
201     pt3 = PseudoThread(f("c"), gobject.PRIORITY_HIGH_IDLE)
202     pt3.start()
203     gtk.main()