Codebase list onioncircuits / 9238370
Add documentation Tails developers 9 years ago
1 changed file(s) with 152 addition(s) and 2 deletion(s). Raw diff Collapse all Expand all
4343 logging.getLogger('stem').setLevel(logging.WARNING)
4444
4545 class TorMonitorWindow(Gtk.ApplicationWindow):
46 """Tor Monitor main window
47
48 This class contains all the UI and the logic to update.
49 """
4650
4751 # Constants used to distinguish circuits and streams in our TreeStore
4852 TYPE_CIRC = 0
4953 TYPE_STREAM = 1
5054
5155 def __init__(self, app):
56 """Create a new TorMonitorWindow
57
58 :var Gtk.Application app: the application which own the window
59 """
5260 Gtk.Window.__init__(self, application=app)
5361
5462 self._listeners_initialized = False
7078 self._infobar.show()
7179
7280 def _create_ui(self):
81 """Creates the user interface
82 """
7383 self.set_default_size(600, 400)
7484 self.set_icon_name('tormonitor')
7585 self.connect('delete-event', self.delete_event_cb)
129139 self.show_all()
130140
131141 def delete_event_cb(self, widget, event, data=None):
142 """Hide the window immediately on close.
143
144 This callback is connected to window's 'delete-event' and forces Gtk to
145 hide the window while waiting for all threads to close.
146 """
132147 logging.info("quitting, waiting all threads to return...")
133148 self.hide()
134149 while Gtk.events_pending():
139154 # =====================
140155
141156 def init_listeners(self):
142 # Connect our handlers to Tor event listeners. Our handlers won't be
143 # executed in the main thread.
157 """Connect our handlers to Tor event listeners
158 """
159 # These handlers won't be executed in the main thread, they will have to
160 # do the real work in another method executed in the main thread by
161 # GLib.idle_add in order not to make Gtk crazy.
144162 if not self._listeners_initialized:
145163 self.controller.add_event_listener(self.update_circ_handler,
146164 stem.control.EventType.CIRC)
150168 self._listeners_initialized = True
151169
152170 def update_circ_handler(self, circ_event):
171 """Handler for stem.control.EventType.CIRC
172 """
153173 # Handle the event in main thread
154174 GLib.idle_add(self.update_circ_cb, circ_event)
155175
156176 def update_stream_handler(self, stream_event):
177 """Handler for stem.control.EventType.STREAM
178 """
157179 # Handle the event in main thread
158180 GLib.idle_add(self.update_stream_cb, stream_event)
159181
160182 def update_status_handler(self, controller, state, timestamp):
183 """Handler for stem.control.BaseController.add_status_listener
184 """
161185 if state == stem.control.State.CLOSED:
162186 GLib.idle_add(self.connection_closed_cb)
163187 GLib.timeout_add_seconds(1, self.controller_reconnect_cb)
168192 # =======================
169193
170194 def connection_closed_cb(self):
195 """Update the UI after we lost conection to the Tor daemon
196
197 This callback is called when we lost connection with the Tor daemon.
198
199 :returns: **False**
200 """
171201 logging.debug("Controller connection closed")
172202 self._hbox.set_sensitive(False)
173203 self._infobar_label.set_text(_("Lost connection to the Tor daemon. "
174204 "Tor Monitor will try to reconnect..."))
175205 self._infobar.set_message_type(Gtk.MessageType.ERROR)
176206 self._infobar.show()
207 self._treestore.clear()
177208 return False
178209
179210 def connection_init_cb(self):
211 """Update the UI after a (re)connection to the Tor daemon
212
213 This callback is called when we (re)connect to the Tor daemon.
214
215 :returns: **False**
216 """
180217 logging.debug("Controller initialized")
181218 self._hbox.set_sensitive(True)
182219 self._infobar_label.set_text(_("Reconnected to the Tor daemon! "
189226 return False
190227
191228 def controller_reconnect_cb(self):
229 """Try to reconnect to the Tor daemon
230
231 This callback is called regularly by self.update_status_handler if the
232 connection to the Tor daemon is lost. It calls self.connection_init_cb
233 after a successful reconnection.
234
235 :returns: **bool** that's **False** if we reconnected successfully and
236 **True** if we failed to reconnect (so that GLib.timeout_add will call
237 the method again).
238 """
192239 logging.debug("Trying to reconnect the controller")
193240 try:
194241 self.controller.connect()
199246 return False
200247
201248 def controller_connect_cb(self):
249 """Try to connect to the Tor daemon for the 1st time
250
251 This callback is called regularly by self.__init__ if there is no
252 connection to the Tor daemon at startup. It calls
253 self.connection_init_cb after a successful connection.
254
255 :returns: **bool** that's **False** if we connected successfully and
256 **True** if we failed to connect (so that GLib.timeout_add will call
257 the method again).
258 """
202259 logging.debug("Trying to connect the controller")
203260 controller = self.get_application().connect_controller()
204261 if controller:
212269 # =========================
213270
214271 def remove_treeiter(self, treeiter):
272 """Remove a treeiter from our circuits/streams list if it is valid
273
274 :var Gtk.TreeIter treeiter: the treeiter to remove
275
276 :returns: **False**
277 """
215278 if self._treestore.iter_is_valid(treeiter):
216279 self._treestore.remove(treeiter)
217280 else:
226289
227290 @staticmethod
228291 def circuit_label(circuit):
292 """Returns a label for a circuit
293
294 :var stem.response.events.CircuitEvent circuit: the circuit
295
296 :returns: **str** representing the circuit
297 """
229298 if circuit.path:
230299 circ_str = _(', ').join([nick for fp, nick in circuit.path])
231300 else:
233302 return circ_str
234303
235304 def add_circuit(self, circuit):
305 """Adds a circuit to our circuits/streams list
306
307 :var stem.response.events.CircuitEvent circuit: the circuit
308
309 :returns: the :class:`Gtk.TreeIter` corresponding to the circuit
310 """
236311 circ_iter = self._treestore.append(None,
237312 [self.TYPE_CIRC,
238313 circuit.id,
242317 return circ_iter
243318
244319 def update_circuit(self, circuit):
320 """Updates a circuit in our circuits/streams list
321
322 :var stem.response.events.CircuitEvent circuit: the circuit
323 """
245324 logging.debug("updating circuit %s" % circuit)
246325 if circuit.reason:
247326 status = _("%s: %s") % (str(circuit.status).capitalize(),
254333 3, status)
255334
256335 def remove_circuit(self, circuit):
336 """Remove a circuit from our circuits/streams list
337
338 :var stem.response.events.CircuitEvent circuit: the circuit
339 """
257340 self._treestore.remove(self._circ_to_iter[circuit.id])
258341 del self._circ_to_iter[circuit.id]
259342
260343 def remove_circuit_delayed(self, circuit):
344 """Remove a circuit from our circuits/streams list after a delay
345
346 The delay gives the user time to read the reason of the removal.
347
348 :var stem.response.events.CircuitEvent circuit: the circuit
349 """
261350 circ_iter = self._circ_to_iter[circuit.id]
262351 del self._circ_to_iter[circuit.id]
263352 GLib.timeout_add_seconds(5, self.remove_treeiter, circ_iter)
264353
265354 def update_circ_cb(self, circ_event):
355 """Updates the circuits/streams list in response to a the
356 :class:`stem.response.events.CircuitEvent`
357
358 :var stem.response.events.CircuitEvent circ_event: the circuit event
359 """
266360 if circ_event.id not in self._circ_to_iter:
267361 self.add_circuit(circ_event)
268362 else:
276370
277371 @staticmethod
278372 def stream_label(stream):
373 """Returns a label for a stream
374
375 :var stem.response.events.StreamEvent stream: the stream
376
377 :returns: **str** representing the stream
378 """
279379 return "%s" % stream.target
280380
281381 def add_stream(self, stream):
382 """Adds a circuit to our circuits/streams list
383
384 :var stem.response.events.StreamEvent stream: the stream
385
386 :returns: the :class:`Gtk.TreeIter` corresponding to the stream
387 """
282388 if not stream.circ_id:
283389 return None
284390 circ_iter = self._circ_to_iter[stream.circ_id]
295401 return stream_iter
296402
297403 def update_stream(self, stream):
404 """Updates a stream in our circuits/streams list
405
406 :var stem.response.events.StreamEvent stream: the stream
407 """
298408 stream_iter = self._stream_to_iter[stream.id]
299409 if stream.circ_id != self._treestore.get_value(stream_iter, 1):
300410 # The stream doesn't belong its parent circuit anymore. Remove it.
309419 self._treestore.set(stream_iter, 3, str(stream.status).capitalize())
310420
311421 def remove_stream(self, stream):
422 """Remove a stream from our circuits/streams list
423
424 :var stem.response.events.StreamEvent stream: the stream
425 """
312426 self._treestore.remove(self._stream_to_iter[stream.id])
313427 del self._stream_to_iter[stream.id]
314428
315429 def remove_stream_delayed(self, stream):
430 """Remove a stream from our circuits/streams list after a delay
431
432 The delay gives the user time to read the reason of the removal.
433
434 :var stem.response.events.StreamEvent stream: the stream
435 """
316436 stream_iter = self._stream_to_iter[stream.id]
317437 if stream_iter:
318438 del self._stream_to_iter[stream.id]
319439 GLib.timeout_add_seconds(5, self.remove_treeiter, stream_iter)
320440
321441 def update_stream_cb(self, stream_event):
442 """Updates the circuits/streams list in response to a the
443 :class:`stem.response.events.StreamEvent`
444
445 :var stem.response.events.StreamEvent stream_event: the stream event
446 """
322447 if stream_event.id not in self._stream_to_iter:
323448 self.add_stream(stream_event)
324449 else:
329454 self.remove_stream_delayed(stream_event)
330455
331456 def populate_treeview(self):
457 """Synchronize the circuits/streams list with the Tor daemon
458 """
332459 self._treestore.clear()
333460 self._circ_to_iter = {}
334461 self._stream_to_iter = {}
343470 # ===============
344471
345472 def cb_treeselection_changed(self, treeselection, data=None):
473 """Handle selection change in the circuits/streams list
474
475 Display details for the circuit selected in the circuits/streams list
476
477 :var Gtk.TreeSelection treeselection: the selection
478
479 :returns: **True**
480 """
346481 (model, selected_iter) = treeselection.get_selected()
347482 if not selected_iter:
348483 return False
362497 return False
363498
364499 def show_circuit_details(self, circuit):
500 """Display details for a circuit
501
502 :var stem.response.events.CircuitEvent circuit: the circuit
503 """
365504 logging.debug("looking up details for %s" % circuit)
366505
367506 # Replace the old content of _path by a fresh ListBox.
375514 self._path.show_all()
376515
377516 def display_node(self, status_entry):
517 """Display details for a node
518
519 :var stem.descriptor.router_status_entry.RouterStatusEntryMicroV3
520 status_entry: the status entry for the node
521 """
378522 country = self.controller.get_info("ip-to-country/%s" % status_entry.address)
379523 if pycountry:
380524 country = pycountry.countries.get(alpha2=country.upper()).name
407551 grid.show_all()
408552
409553 class TorMonitorApplication(Gtk.Application):
554 """Tor Monitor application
555
556 :var stem.control.Controller controller: a controller to the Tor daemon
557 """
410558
411559 def __init__(self):
412560 Gtk.Application.__init__(self)
414562 self.connect_controller()
415563
416564 def connect_controller(self):
565 """Connects the controller to the Tor daemon.
566 """
417567 self.controller = stem.connection.connect_socket_file()
418568 return self.controller
419569