1 '''
2 Defines a threaded proxy for L{AEOutput.Audio} devices.
3
4 @author: Brett Clippingdale
5 @author: Peter Parente
6 @organization: IBM Corporation
7 @copyright: Copyright (c) 2005, 2007 IBM Corporation
8 @license: The BSD License
9
10 All rights reserved. This program and the accompanying materials are made
11 available under the terms of the BSD license which accompanies
12 this distribution, and is available at
13 U{http://www.opensource.org/licenses/bsd-license.php}
14 '''
15
16 import threading, Queue
17 import Base
18 import AEConstants
19
21 '''
22 Buffers calls to methods on a L{AEOutput.Audio} device in a secondary thread
23 and executes them some time later when that thread runs.
24
25 @ivar device: Device on which to invoke send* methods
26 @type device: L{AEOutput.Audio}
27 @ivar want_stop: Flag indicating a stop is requested
28 @type want_stop: boolean
29 @ivar just_stopped: Flag indicating that the last written command was a stop.
30 Used to avoid unnecessary stops
31 @type just_stopped: boolean
32 @ivar lock: Semaphore used to ensure no commands are buffered while the
33 buffer is being reset after sending a stop
34 @type lock: threading.Sempahore
35 @ivar init_event: Event used to block non-threaded calls to the device until
36 the device has been initialized in the context of the running thread.
37 @type init_event: threading.Event
38 @ivar data_buffer: Buffer of commands to be sent to the output device
39 @type data_buffer: Queue.Queue
40 @ivar alive: Is the thread running or not?
41 @type alive: boolean
42 '''
44 '''
45 Initializes the parent class and stores the device reference. Creates a
46 queue that will buffer commands to the speech device. Creates flags used
47 for indicating whether a stop is requested or has been requested recently.
48 Creates a semaphore used to ensure that no commands can be added to the
49 buffer while it is being reset by a stop command.
50
51 @param device: The device reference to use for writing commands
52 @type device: L{AEOutput.Audio}
53 '''
54 threading.Thread.__init__(self)
55 Base.AEOutput.__init__(self)
56 self.device = device
57 self.want_stop = False
58 self.just_stopped = False
59 self.lock = threading.Semaphore()
60 self.init_event = threading.Event()
61 self.data_buffer = Queue.Queue()
62 self.alive = False
63
65 '''
66 Called after the instance is created to start the device running. The
67 device's init method is called in the context of the running thread's
68 L{run} method before the thread enters its loop.
69 '''
70 self.alive = True
71 self.start()
72
74 '''
75 Gets the capabilities of the proxied device. This method is called in the
76 context of the caller, not the running thread.
77
78 @return: List of capability names
79 @rtype: list of string
80 '''
81 return self.device.getCapabilities()
82
84 '''
85 Called after the L{init} method by the L{DeviceManager} to ensure that the
86 device is functioning before time is spent unserializing its style data.
87
88 Calls the init method on the default style object and provides it with a
89 reference to this initialized device. Then tries to load the persisted
90 setting values from disk. If that fails, the L{DeviceManager} will try to
91 call L{createDistinctStyles} instead.
92
93 This method is called in the context of the caller, not the running thread.
94 It blocks until the L{init_event} is set indicating the device has been
95 initialized in the second thread.
96
97 @param sett_man: Instance of the settings manager
98 @type sett_man: L{SettingsManager}
99 @raise KeyError: When styles have not previously been persisted for this
100 device
101 @raise OSError: When the profile file cannot be opened or read
102 '''
103 self.init_event.wait()
104 self.device.loadStyles(sett_man)
105
107 '''
108 Persists styles to disk. Called after the L{close} method by the
109 L{DeviceManager} to ensure the device is properly shutdown before
110 serializing its data. This method is called in the context of the caller,
111 not the running thread.
112
113 @param sett_man: Instance of the settings manager
114 @type sett_man: L{SettingsManager}
115 @raise KeyError: When styles have not previously been persisted for this
116 device
117 @raise OSError: When the profile file cannot be opened or read
118 '''
119 self.device.saveStyles(sett_man)
120
122 '''
123 Stores the style object under the given key. The style object should be one
124 previously generated by this device (e.g. using L{createDistinctStyles})
125 but it is not an enforced requirement. Always makes the style clean before
126 storing it. This method is called in the context of the caller, not the
127 running thread.
128
129 @param key: Any immutable object
130 @type key: immutable
131 @param style: L{AEOutput} subclass of L{AEState}
132 @type style: L{AEState}
133 '''
134 self.device.setStyle(key, style)
135
137 '''
138 Gets the style object stored under the given key. If the key is unknown,
139 returns an empty flyweight backed by the default style and stores the new
140 style in L{styles}. This method is called in the context of the caller, not
141 the running thread.
142
143 @param key: Any immutable object
144 @type key: immutable
145 @return: L{AEOutput} subclass of L{AEState}
146 @rtype: L{AEState}
147 '''
148 return self.device.getStyle(key)
149
151 '''
152 Creates up to the given number of styles for this device. This method is
153 called in the context of the caller, not the running thread. It blocks
154 until the L{init_event} is set indicating the device has been initialized
155 in the second thread.
156
157 @param num_groups: Number of sematic groups the requestor would like to
158 represent using distinct styles
159 @type num_groups: integer
160 @param num_layers: Number of content origins (e.g. output originating from
161 a background task versus the focus) the requestor would like to represent
162 using distinct styles
163 @type num_layers: integer
164 @return: New styles
165 @rtype: list of L{AEOutput.Style}
166 '''
167 self.init_event.wait()
168 return self.device.createDistinctStyles(num_groups, num_layers)
169
171 '''
172 Creates up to the given number of styles for this device. This method is
173 called in the context of the caller, not the running thread. It blocks
174 until the L{init_event} is set indicating the device has been initialized
175 in the second thread.
176
177 @return: Default style
178 @rtype: L{AEOutput.Style}
179 '''
180 self.init_event.wait()
181 return self.device.getDefaultStyle()
182
184 '''
185 Stops the running thread. Puts a null callable in the queue to wake the
186 thread if it is sleeping.
187 '''
188 self.alive = False
189 self._put(lambda : None)
190
192 '''
193 Returns this object as the proxy for itself because a thread proxying for
194 another thread proxying for a device is not supported.
195
196 @return: self
197 @rtype: L{AudioThreadProxy}
198 '''
199 return self
200
202 '''
203 Gives the user displayable (localized) name for this output device.
204 Relevant version and device status should be included. This method is
205 called in the context of the caller, not the running thread.
206
207 @return: The localized name for the device
208 @rtype: string
209 '''
210 return self.device.getName()
211
212 - def send(self, name, value, style=None):
246
248 '''
249 Indicates whether the device is active (giving output) or not.
250
251 @return: True when content is buffered or the device is outputing
252 @rtype: boolean
253 @raise NotImplementedError: When not overriden in a subclass
254 '''
255 return (self.data_buffer.qsize() != 0 or self.device.isActive())
256
258 '''
259 Parses the string using the implementation provided by the proxied device.
260 This method is called in the context of the caller, not the running thread.
261
262 @param text: Text to be parsed
263 @type text: string
264 @param style: Style object defining how the text should be parsed
265 @type style: L{AEOutput.Style}
266 @param por: Point of regard for the first character in the text, or None if
267 the text is not associated with a POR
268 @type por: L{POR}
269 @param sem: Semantic tag for the text
270 @type sem: integer
271 @return: Parsed words
272 @rtype: 3-tuple of lists of string, L{POR}, L{AEOutput.Style}
273 '''
274 return self.device.parseString(text, style, por, sem)
275
276 - def _put(self, mtd, *args):
277 '''
278 Buffers any non-stop command in the queue and returns immediately. Sets the
279 L{want_stop} flag for a stop command if neither L{want_stop} nor
280 L{just_stopped} is set. Blocks on entry if the thread is busy clearing out
281 the buffer in response to a previous stop command. Leaves the lock set if
282 a new stop command is buffered. It will be unset when the command is
283 processed.
284
285 @param mtd: Device method to call at a later time
286 @type mtd: callable
287 @param args: Additional positional arguments to be passed to the device
288 when the method is invoked
289 @type args: list
290 '''
291 self.lock.acquire()
292 if (mtd is self.device.sendStop):
293 if not self.want_stop and not self.just_stopped:
294
295 self.want_stop = True
296
297 self.data_buffer.put_nowait((mtd, args))
298
299
300
301 return
302 else:
303 self.data_buffer.put_nowait((mtd, args))
304
305 self.just_stopped = False
306 self.lock.release()
307
309 '''
310 Runs the thread until alive is not longer True. Sleeps until methods and
311 arguments to be applied are put in the L{data_buffer}. Wakes up and
312 invokes all buffered methods and arguments. Initializes the device before
313 entering the loop and closes it after leaving the loop.
314 '''
315 self.device.init()
316 self.init_event.set()
317 while self.alive:
318
319 mtd, args = self.data_buffer.get()
320
321
322 if self.want_stop:
323
324 self.data_buffer = Queue.Queue()
325
326 self.device.sendStop()
327
328 self.want_stop = False
329
330 self.just_stopped = True
331
332 self.lock.release()
333 else:
334
335 try:
336 mtd(*args)
337 except NotImplementedError:
338 pass
339 self.device.close()
340