class DebugWindow(QMainWindow): """ The debug window shows various statistics about Tribler such as performed requests, IPv8 statistics and community information. """ resize_event = pyqtSignal() def __init__(self, settings, tribler_version): self._logger = logging.getLogger(self.__class__.__name__) QMainWindow.__init__(self) self.cpu_plot = None self.memory_plot = None self.initialized_cpu_plot = False self.initialized_memory_plot = False self.cpu_plot_timer = None self.memory_plot_timer = None self.tribler_version = tribler_version self.profiler_enabled = False self.toggling_profiler = False uic.loadUi(get_ui_file_path('debugwindow.ui'), self) self.setWindowTitle("Tribler debug pane") self.window().dump_memory_core_button.clicked.connect(lambda: self.on_memory_dump_button_clicked(True)) self.window().dump_memory_gui_button.clicked.connect(lambda: self.on_memory_dump_button_clicked(False)) self.window().toggle_profiler_button.clicked.connect(self.on_toggle_profiler_button_clicked) self.window().debug_tab_widget.setCurrentIndex(0) self.window().ipv8_tab_widget.setCurrentIndex(0) self.window().tunnel_tab_widget.setCurrentIndex(0) self.window().dht_tab_widget.setCurrentIndex(0) self.window().system_tab_widget.setCurrentIndex(0) self.window().debug_tab_widget.currentChanged.connect(self.tab_changed) self.window().ipv8_tab_widget.currentChanged.connect(self.ipv8_tab_changed) self.window().tunnel_tab_widget.currentChanged.connect(self.tunnel_tab_changed) self.window().dht_tab_widget.currentChanged.connect(self.dht_tab_changed) self.window().events_tree_widget.itemClicked.connect(self.on_event_clicked) self.window().system_tab_widget.currentChanged.connect(self.system_tab_changed) self.load_general_tab() self.window().open_files_tree_widget.header().setSectionResizeMode(0, QHeaderView.Stretch) # Enable/disable tabs, based on settings self.window().debug_tab_widget.setTabEnabled(2, settings and settings['trustchain']['enabled']) self.window().debug_tab_widget.setTabEnabled(3, settings and settings['ipv8']['enabled']) self.window().system_tab_widget.setTabEnabled(3, settings and settings['resource_monitor']['enabled']) self.window().system_tab_widget.setTabEnabled(4, settings and settings['resource_monitor']['enabled']) # Refresh logs self.window().log_refresh_button.clicked.connect(lambda: self.load_logs_tab()) self.window().log_tab_widget.currentChanged.connect(lambda index: self.load_logs_tab()) # IPv8 statistics enabled? self.ipv8_statistics_enabled = settings['ipv8']['statistics'] # Libtorrent tab self.init_libtorrent_tab() # Position to center frame_geometry = self.frameGeometry() screen = QDesktopWidget().screenNumber(QDesktopWidget().cursor().pos()) center_point = QDesktopWidget().screenGeometry(screen).center() frame_geometry.moveCenter(center_point) self.move(frame_geometry.topLeft()) # Refresh timer self.refresh_timer = None self.rest_request = None self.ipv8_health_widget = None def hideEvent(self, hide_event): self.stop_timer() self.hide_ipv8_health_widget() def showEvent(self, show_event): if self.ipv8_health_widget and self.ipv8_health_widget.isVisible(): self.ipv8_health_widget.resume() TriblerNetworkRequest("ipv8/asyncio/drift", self.on_ipv8_health_enabled, data={"enable": True}, method='PUT') def run_with_timer(self, call_fn, timeout=DEBUG_PANE_REFRESH_TIMEOUT): call_fn() self.stop_timer() self.refresh_timer = QTimer() self.refresh_timer.setSingleShot(True) self.refresh_timer.timeout.connect( lambda _call_fn=call_fn, _timeout=timeout: self.run_with_timer(_call_fn, timeout=_timeout) ) self.refresh_timer.start(timeout) def stop_timer(self): if self.refresh_timer: try: self.refresh_timer.stop() self.refresh_timer.deleteLater() except RuntimeError: self._logger.error("Failed to stop refresh timer in Debug pane") def init_libtorrent_tab(self): self.window().libtorrent_tab_widget.setCurrentIndex(0) self.window().libtorrent_tab_widget.currentChanged.connect(lambda _: self.load_libtorrent_data(export=False)) self.window().lt_zero_hop_btn.clicked.connect(lambda _: self.load_libtorrent_data(export=False)) self.window().lt_one_hop_btn.clicked.connect(lambda _: self.load_libtorrent_data(export=False)) self.window().lt_two_hop_btn.clicked.connect(lambda _: self.load_libtorrent_data(export=False)) self.window().lt_three_hop_btn.clicked.connect(lambda _: self.load_libtorrent_data(export=False)) self.window().lt_export_btn.clicked.connect(lambda _: self.load_libtorrent_data(export=True)) self.window().lt_zero_hop_btn.setChecked(True) def tab_changed(self, index): if index == 0: self.load_general_tab() elif index == 1: self.load_requests_tab() elif index == 2: self.run_with_timer(self.load_trustchain_tab) elif index == 3: self.ipv8_tab_changed(self.window().ipv8_tab_widget.currentIndex()) elif index == 4: self.tunnel_tab_changed(self.window().tunnel_tab_widget.currentIndex()) elif index == 5: self.dht_tab_changed(self.window().dht_tab_widget.currentIndex()) elif index == 6: self.run_with_timer(self.load_events_tab) elif index == 7: self.system_tab_changed(self.window().system_tab_widget.currentIndex()) elif index == 8: self.load_libtorrent_data() elif index == 9: self.load_logs_tab() def ipv8_tab_changed(self, index): if index == 0: self.run_with_timer(self.load_ipv8_general_tab) elif index == 1: self.run_with_timer(self.load_ipv8_communities_tab) elif index == 2: self.run_with_timer(self.load_ipv8_community_details_tab) elif index == 3: self.run_with_timer(self.load_ipv8_health_monitor) def tunnel_tab_changed(self, index): if index == 0: self.run_with_timer(self.load_tunnel_circuits_tab) elif index == 1: self.run_with_timer(self.load_tunnel_relays_tab) elif index == 2: self.run_with_timer(self.load_tunnel_exits_tab) elif index == 3: self.run_with_timer(self.load_tunnel_swarms_tab) elif index == 4: self.run_with_timer(self.load_tunnel_peers_tab) def dht_tab_changed(self, index): if index == 0: self.run_with_timer(self.load_dht_statistics_tab) elif index == 1: self.run_with_timer(self.load_dht_buckets_tab) def system_tab_changed(self, index): if index == 0: self.load_open_files_tab() elif index == 1: self.load_open_sockets_tab() elif index == 2: self.load_threads_tab() elif index == 3: self.load_cpu_tab() elif index == 4: self.load_memory_tab() elif index == 5: self.load_profiler_tab() def create_and_add_widget_item(self, key, value, widget): item = QTreeWidgetItem(widget) item.setText(0, key) item.setText(1, "%s" % value) widget.addTopLevelItem(item) def load_general_tab(self): TriblerNetworkRequest("statistics/tribler", self.on_tribler_statistics) def on_tribler_statistics(self, data): if not data: return data = data["tribler_statistics"] self.window().general_tree_widget.clear() self.create_and_add_widget_item("Tribler version", self.tribler_version, self.window().general_tree_widget) self.create_and_add_widget_item( "Python version", sys.version.replace('\n', ''), self.window().general_tree_widget # to fit in one line ) self.create_and_add_widget_item("Libtorrent version", libtorrent.version, self.window().general_tree_widget) self.create_and_add_widget_item("", "", self.window().general_tree_widget) self.create_and_add_widget_item("Number of channels", data["num_channels"], self.window().general_tree_widget) self.create_and_add_widget_item( "Database size", format_size(data["db_size"]), self.window().general_tree_widget ) self.create_and_add_widget_item( "Number of known torrents", data["num_torrents"], self.window().general_tree_widget ) self.create_and_add_widget_item("", "", self.window().general_tree_widget) disk_usage = psutil.disk_usage('/') self.create_and_add_widget_item( "Total disk space", format_size(disk_usage.total), self.window().general_tree_widget ) self.create_and_add_widget_item( "Used disk space", format_size(disk_usage.used), self.window().general_tree_widget ) self.create_and_add_widget_item( "Free disk space", format_size(disk_usage.free), self.window().general_tree_widget ) def load_requests_tab(self): self.window().requests_tree_widget.clear() for request, status_code in sorted(tribler_performed_requests, key=lambda rq: rq[0].time): endpoint = request.url method = request.method data = request.raw_data timestamp = request.time item = QTreeWidgetItem(self.window().requests_tree_widget) item.setText(0, "%s %s %s" % (method, repr(endpoint), repr(data))) item.setText(1, ("%d" % status_code) if status_code else "unknown") item.setText(2, "%s" % strftime("%H:%M:%S", localtime(timestamp))) self.window().requests_tree_widget.addTopLevelItem(item) def load_trustchain_tab(self): TriblerNetworkRequest("trustchain/statistics", self.on_trustchain_statistics) def on_trustchain_statistics(self, data): if not data: return self.window().trustchain_tree_widget.clear() for key, value in data["statistics"].items(): self.create_and_add_widget_item(key, value, self.window().trustchain_tree_widget) def load_ipv8_general_tab(self): TriblerNetworkRequest("statistics/ipv8", self.on_ipv8_general_stats) def on_ipv8_general_stats(self, data): if not data: return self.window().ipv8_general_tree_widget.clear() for key, value in data["ipv8_statistics"].items(): if key in ('total_up', 'total_down'): value = "%.2f MB" % (value / (1024.0 * 1024.0)) elif key == 'session_uptime': value = "%s" % str(datetime.timedelta(seconds=int(value))) self.create_and_add_widget_item(key, value, self.window().ipv8_general_tree_widget) def load_ipv8_communities_tab(self): TriblerNetworkRequest("ipv8/overlays", self.on_ipv8_community_stats) def _colored_peer_count(self, peer_count, overlay_count, overlay_name): if overlay_name == 'DiscoveryCommunity': limits = [20, overlay_count * 30 + 1] elif overlay_name == 'DHTDiscoveryCommunity': limits = [20, 61] elif overlay_name == 'RemoteQueryCommunity': limits = [20, 51] else: limits = [20, 31] color = 0xF4D03F if peer_count < limits[0] else (0x56F129 if peer_count < limits[1] else 0xF12929) return QBrush(QColor(color)) def on_ipv8_community_stats(self, data): if not data: return self.window().communities_tree_widget.clear() for overlay in data["overlays"]: item = QTreeWidgetItem(self.window().communities_tree_widget) item.setText(0, overlay["overlay_name"]) item.setText(1, overlay["master_peer"][-12:]) item.setText(2, overlay["my_peer"][-12:]) peer_count = len(overlay["peers"]) item.setText(3, "%s" % peer_count) item.setForeground(3, self._colored_peer_count(peer_count, len(data["overlays"]), overlay["overlay_name"])) if "statistics" in overlay and overlay["statistics"]: statistics = overlay["statistics"] item.setText(4, "%.3f" % (statistics["bytes_up"] / (1024.0 * 1024.0))) item.setText(5, "%.3f" % (statistics["bytes_down"] / (1024.0 * 1024.0))) item.setText(6, "%s" % statistics["num_up"]) item.setText(7, "%s" % statistics["num_down"]) item.setText(8, "%.3f" % statistics["diff_time"]) else: item.setText(4, "N/A") item.setText(5, "N/A") item.setText(6, "N/A") item.setText(7, "N/A") item.setText(8, "N/A") self.window().communities_tree_widget.addTopLevelItem(item) map(self.window().communities_tree_widget.resizeColumnToContents, range(10)) def load_ipv8_community_details_tab(self): if self.ipv8_statistics_enabled: self.window().ipv8_statistics_error_label.setHidden(True) TriblerNetworkRequest("ipv8/overlays/statistics", self.on_ipv8_community_detail_stats) else: self.window().ipv8_statistics_error_label.setHidden(False) self.window().ipv8_communities_details_widget.setHidden(True) def on_ipv8_community_detail_stats(self, data): if not data: return self.window().ipv8_communities_details_widget.setHidden(False) self.window().ipv8_communities_details_widget.clear() for overlay in data["statistics"]: self.window().ipv8_communities_details_widget.setColumnWidth(0, 250) for key, stats in overlay.items(): header_item = QTreeWidgetItem(self.window().ipv8_communities_details_widget) header_item.setFirstColumnSpanned(True) header_item.setBackground(0, QtGui.QColor('#CCCCCC')) header_item.setText(0, key) self.window().ipv8_communities_details_widget.addTopLevelItem(header_item) for request_id, stat in stats.items(): stat_item = QTreeWidgetItem(self.window().ipv8_communities_details_widget) stat_item.setText(0, request_id) stat_item.setText(1, "%.3f" % (stat["bytes_up"] / (1024.0 * 1024.0))) stat_item.setText(2, "%.3f" % (stat["bytes_down"] / (1024.0 * 1024.0))) stat_item.setText(3, "%s" % stat["num_up"]) stat_item.setText(4, "%s" % stat["num_down"]) self.window().ipv8_communities_details_widget.addTopLevelItem(stat_item) def load_ipv8_health_monitor(self): """ Lazy load and enable the IPv8 core health monitor. """ if self.ipv8_health_widget is None: # Add the core monitor widget to the tab widget. from PyQt5.QtWidgets import QVBoxLayout widget = MonitorWidget() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(widget) self.window().ipv8_health_monitor_widget.setLayout(layout) self.window().ipv8_health_monitor_widget.show() self.ipv8_health_widget = widget else: # We already loaded the widget, just resume it. self.ipv8_health_widget.resume() # Whether the widget is newly loaded or not, start the measurements. TriblerNetworkRequest("ipv8/asyncio/drift", self.on_ipv8_health_enabled, data={"enable": True}, method='PUT') def hide_ipv8_health_widget(self): """ We need to hide the IPv8 health widget, involving two things: 1. Stop the smooth graphical updates in the widget. 2. Remove the observer from the IPv8 core. """ if self.ipv8_health_widget is not None and not self.ipv8_health_widget.is_paused: self.ipv8_health_widget.pause() TriblerNetworkRequest("ipv8/asyncio/drift", lambda _: None, data={"enable": False}, method='PUT') def on_ipv8_health(self, data): """ Measurements came in, send them to the widget for "plotting". """ if not data or 'measurements' not in data or self.ipv8_health_widget is None: return self.ipv8_health_widget.set_history(data['measurements']) def on_ipv8_health_enabled(self, data): """ The request to enable IPv8 completed. Start requesting measurements. """ if not data: return self.run_with_timer(lambda: TriblerNetworkRequest("ipv8/asyncio/drift", self.on_ipv8_health), 100) def add_items_to_tree(self, tree, items, keys): tree.clear() for item in items: widget_item = QTreeWidgetItem(tree) for index, key in enumerate(keys): if key in ["bytes_up", "bytes_down"]: value = format_size(item[key]) elif key in ["creation_time", "last_lookup"]: value = str(datetime.timedelta(seconds=int(time() - item[key]))) if item[key] > 0 else '-' else: value = str(item[key]) widget_item.setText(index, value) tree.addTopLevelItem(widget_item) def load_tunnel_circuits_tab(self): self.window().circuits_tree_widget.setColumnWidth(3, 200) TriblerNetworkRequest("ipv8/tunnel/circuits", self.on_tunnel_circuits) def on_tunnel_circuits(self, circuits): if not circuits: return for c in circuits["circuits"]: c["hops"] = f"{c['goal_hops']} / {c['goal_hops']}" c["exit_flags"] = c["exit_flags"] if c["state"] == "READY" else "" self.add_items_to_tree( self.window().circuits_tree_widget, circuits.get("circuits"), ["circuit_id", "hops", "type", "state", "bytes_up", "bytes_down", "creation_time", "exit_flags"], ) def load_tunnel_relays_tab(self): TriblerNetworkRequest("ipv8/tunnel/relays", self.on_tunnel_relays) def on_tunnel_relays(self, data): if data: self.add_items_to_tree( self.window().relays_tree_widget, data["relays"], ["circuit_from", "circuit_to", "is_rendezvous", "bytes_up", "bytes_down", "creation_time"], ) def load_tunnel_exits_tab(self): TriblerNetworkRequest("ipv8/tunnel/exits", self.on_tunnel_exits) def on_tunnel_exits(self, data): if data: self.add_items_to_tree( self.window().exits_tree_widget, data["exits"], ["circuit_from", "enabled", "bytes_up", "bytes_down", "creation_time"], ) def load_tunnel_swarms_tab(self): TriblerNetworkRequest("ipv8/tunnel/swarms", self.on_tunnel_swarms) def on_tunnel_swarms(self, data): if data: self.add_items_to_tree( self.window().swarms_tree_widget, data.get("swarms"), [ "info_hash", "num_seeders", "num_connections", "num_connections_incomplete", "seeding", "last_lookup", "bytes_up", "bytes_down", ], ) def load_tunnel_peers_tab(self): self.window().peers_tree_widget.setColumnWidth(2, 300) TriblerNetworkRequest("ipv8/tunnel/peers", self.on_tunnel_peers) def on_tunnel_peers(self, data): if data: self.add_items_to_tree( self.window().peers_tree_widget, data.get("peers"), ["ip", "port", "mid", "is_key_compatible", "flags"] ) def load_dht_statistics_tab(self): TriblerNetworkRequest("ipv8/dht/statistics", self.on_dht_statistics) def on_dht_statistics(self, data): if not data: return self.window().dhtstats_tree_widget.clear() for key, value in data["statistics"].items(): self.create_and_add_widget_item(key, value, self.window().dhtstats_tree_widget) def load_dht_buckets_tab(self): TriblerNetworkRequest("ipv8/dht/buckets", self.on_dht_buckets) def on_dht_buckets(self, data): if data: for bucket in data["buckets"]: bucket["num_peers"] = len(bucket["peers"]) ts = bucket["last_changed"] bucket["last_changed"] = str(datetime.timedelta(seconds=int(time() - ts))) if ts > 0 else '-' self.add_items_to_tree( self.window().buckets_tree_widget, data.get("buckets"), [ "prefix", "last_changed", "num_peers" ], ) def on_event_clicked(self, item): event_dict = item.data(0, Qt.UserRole) self.window().event_text_box.setPlainText(json.dumps(event_dict)) def load_events_tab(self): self.window().events_tree_widget.clear() for event_dict, timestamp in tribler_received_events: item = QTreeWidgetItem(self.window().events_tree_widget) item.setData(0, Qt.UserRole, event_dict) item.setText(0, "%s" % event_dict['type']) item.setText(1, "%s" % strftime("%H:%M:%S", localtime(timestamp))) self.window().events_tree_widget.addTopLevelItem(item) def load_open_files_tab(self): # Fill the open files (GUI) tree widget my_process = psutil.Process() self.window().open_files_tree_widget.clear() gui_item = QTreeWidgetItem(self.window().open_files_tree_widget) try: open_files = my_process.open_files() gui_item.setText(0, "GUI (%d)" % len(open_files)) self.window().open_files_tree_widget.addTopLevelItem(gui_item) for open_file in open_files: item = QTreeWidgetItem() item.setText(0, open_file.path) item.setText(1, "%d" % open_file.fd) gui_item.addChild(item) except psutil.AccessDenied as exc: gui_item.setText(0, "Unable to get open files for GUI (%s)" % exc) TriblerNetworkRequest("debug/open_files", self.on_core_open_files) def on_core_open_files(self, data): if not data: return core_item = QTreeWidgetItem(self.window().open_files_tree_widget) core_item.setText(0, "Core (%d)" % len(data["open_files"])) self.window().open_files_tree_widget.addTopLevelItem(core_item) for open_file in data["open_files"]: item = QTreeWidgetItem() item.setText(0, open_file["path"]) item.setText(1, "%d" % open_file["fd"]) core_item.addChild(item) def load_open_sockets_tab(self): TriblerNetworkRequest("debug/open_sockets", self.on_core_open_sockets) def on_core_open_sockets(self, data): if not data: return self.window().open_sockets_tree_widget.clear() self.window().open_sockets_label.setText("Sockets opened by core (%d):" % len(data["open_sockets"])) for open_socket in data["open_sockets"]: if open_socket["family"] == socket.AF_INET: family = "AF_INET" elif open_socket["family"] == socket.AF_INET6: family = "AF_INET6" elif open_socket["family"] == socket.AF_UNIX: family = "AF_UNIX" else: family = "-" item = QTreeWidgetItem(self.window().open_sockets_tree_widget) item.setText(0, open_socket["laddr"]) item.setText(1, open_socket["raddr"]) item.setText(2, family) item.setText(3, "SOCK_STREAM" if open_socket["type"] == socket.SOCK_STREAM else "SOCK_DGRAM") item.setText(4, open_socket["status"]) self.window().open_sockets_tree_widget.addTopLevelItem(item) def load_threads_tab(self): TriblerNetworkRequest("debug/threads", self.on_core_threads) def on_core_threads(self, data): if not data: return self.window().threads_tree_widget.clear() for thread_info in data["threads"]: thread_item = QTreeWidgetItem(self.window().threads_tree_widget) thread_item.setText(0, "%d" % thread_info["thread_id"]) thread_item.setText(1, thread_info["thread_name"]) self.window().threads_tree_widget.addTopLevelItem(thread_item) for frame in thread_info["frames"]: frame_item = QTreeWidgetItem() frame_item.setText(2, frame) thread_item.addChild(frame_item) def load_cpu_tab(self): if not self.initialized_cpu_plot: vlayout = self.window().cpu_plot_widget.layout() self.cpu_plot = CPUPlot(self.window().cpu_plot_widget) vlayout.addWidget(self.cpu_plot) self.initialized_cpu_plot = True self.refresh_cpu_plot() # Start timer self.cpu_plot_timer = QTimer() self.cpu_plot_timer.timeout.connect(self.load_cpu_tab) self.cpu_plot_timer.start(5000) def refresh_cpu_plot(self): TriblerNetworkRequest("debug/cpu/history", self.on_core_cpu_history) def on_core_cpu_history(self, data): if not data: return self.cpu_plot.reset_plot() for cpu_info in data["cpu_history"]: self.cpu_plot.add_data(cpu_info["time"], [round(cpu_info["cpu"], 2)]) self.cpu_plot.render_plot() def load_memory_tab(self): if not self.initialized_memory_plot: vlayout = self.window().memory_plot_widget.layout() self.memory_plot = MemoryPlot(self.window().memory_plot_widget) vlayout.addWidget(self.memory_plot) self.initialized_memory_plot = True self.refresh_memory_plot() # Start timer self.memory_plot_timer = QTimer() self.memory_plot_timer.timeout.connect(self.load_memory_tab) self.memory_plot_timer.start(5000) def load_profiler_tab(self): self.window().toggle_profiler_button.setEnabled(False) TriblerNetworkRequest("debug/profiler", self.on_profiler_info) def on_profiler_info(self, data): if not data: return self.profiler_enabled = data["state"] == "STARTED" self.window().toggle_profiler_button.setEnabled(True) self.window().toggle_profiler_button.setText("%s profiler" % ("Stop" if self.profiler_enabled else "Start")) def on_toggle_profiler_button_clicked(self): if self.toggling_profiler: return self.toggling_profiler = True self.window().toggle_profiler_button.setEnabled(False) method = "DELETE" if self.profiler_enabled else "PUT" TriblerNetworkRequest("debug/profiler", self.on_profiler_state_changed, method=method) def on_profiler_state_changed(self, data): if not data: return self.toggling_profiler = False self.window().toggle_profiler_button.setEnabled(True) self.load_profiler_tab() if 'profiler_file' in data: QMessageBox.about( self, "Profiler statistics saved", "The profiler data has been saved to %s." % data['profiler_file'] ) def refresh_memory_plot(self): TriblerNetworkRequest("debug/memory/history", self.on_core_memory_history) def on_core_memory_history(self, data): if not data or data.get("memory_history") is None: return self.memory_plot.reset_plot() for mem_info in data["memory_history"]: self.memory_plot.add_data(mem_info["time"], [round(mem_info["mem"], 2)]) self.memory_plot.render_plot() def on_memory_dump_button_clicked(self, dump_core): self.export_dir = QFileDialog.getExistingDirectory( self, "Please select the destination directory", "", QFileDialog.ShowDirsOnly ) if len(self.export_dir) > 0: filename = "tribler_mem_dump_%s_%s.json" % ( 'core' if dump_core else 'gui', datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S"), ) if dump_core: self.rest_request = TriblerNetworkRequest( "debug/memory/dump", lambda data, _: self.on_memory_dump_data_available(filename, data) ) elif scanner: scanner.dump_all_objects(os.path.join(self.export_dir, filename)) else: ConfirmationDialog.show_error( self.window(), "Error when performing a memory dump", "meliae memory dumper is not compatible with Python 3", ) def on_memory_dump_data_available(self, filename, data): if not data: return dest_path = os.path.join(self.export_dir, filename) try: with open(dest_path, "wb") as memory_dump_file: memory_dump_file.write(data) except IOError as exc: ConfirmationDialog.show_error( self.window(), "Error when exporting file", "An error occurred when exporting the torrent file: %s" % str(exc), ) def closeEvent(self, close_event): if self.rest_request: self.rest_request.cancel_request() if self.cpu_plot_timer: self.cpu_plot_timer.stop() if self.memory_plot_timer: self.memory_plot_timer.stop() def load_logs_tab(self): # Max lines from GUI max_log_lines = self.window().max_lines_value.text() tab_index = self.window().log_tab_widget.currentIndex() tab_name = "core" if tab_index == 0 else "gui" request_query = "process=%s&max_lines=%s" % (tab_name, max_log_lines) TriblerNetworkRequest("debug/log?%s" % request_query, self.display_logs) def display_logs(self, data): if not data: return tab_index = self.window().log_tab_widget.currentIndex() log_display_widget = ( self.window().core_log_display_area if tab_index == 0 else self.window().gui_log_display_area ) log_display_widget.moveCursor(QTextCursor.End) key_content = u'content' key_max_lines = u'max_lines' if not key_content in data or not data[key_content]: log_display_widget.setPlainText('No logs found') else: log_display_widget.setPlainText(data[key_content]) if not key_max_lines in data or not data[key_max_lines]: self.window().max_lines_value.setText('') else: self.window().max_lines_value.setText(str(data[key_max_lines])) sb = log_display_widget.verticalScrollBar() sb.setValue(sb.maximum()) def show(self): super(DebugWindow, self).show() # this will remove minimized status # and restore window with keeping maximized/normal state self.window().setWindowState(self.window().windowState() & ~Qt.WindowMinimized | Qt.WindowActive) self.window().activateWindow() def load_libtorrent_data(self, export=False): tab = self.window().libtorrent_tab_widget.currentIndex() hop = ( 0 if self.window().lt_zero_hop_btn.isChecked() else 1 if self.window().lt_one_hop_btn.isChecked() else 2 if self.window().lt_two_hop_btn.isChecked() else 3 ) if tab == 0: self.load_libtorrent_settings_tab(hop, export=export) elif tab == 1: self.load_libtorrent_sessions_tab(hop, export=export) def load_libtorrent_settings_tab(self, hop, export=False): TriblerNetworkRequest( "libtorrent/settings?hop=%d" % hop, lambda data: self.on_libtorrent_settings_received(data, export=export) ) self.window().libtorrent_settings_tree_widget.clear() def on_libtorrent_settings_received(self, data, export=False): if not data: return for key, value in data["settings"].items(): item = QTreeWidgetItem(self.window().libtorrent_settings_tree_widget) item.setText(0, key) item.setText(1, str(value)) self.window().libtorrent_settings_tree_widget.addTopLevelItem(item) if export: self.save_to_file("libtorrent_settings.json", data) def load_libtorrent_sessions_tab(self, hop, export=False): TriblerNetworkRequest( "libtorrent/session?hop=%d" % hop, lambda data: self.on_libtorrent_session_received(data, export=export) ) self.window().libtorrent_session_tree_widget.clear() def on_libtorrent_session_received(self, data, export=False): if not data: return for key, value in data["session"].items(): item = QTreeWidgetItem(self.window().libtorrent_session_tree_widget) item.setText(0, key) item.setText(1, str(value)) self.window().libtorrent_session_tree_widget.addTopLevelItem(item) if export: self.save_to_file("libtorrent_session.json", data) def save_to_file(self, filename, data): base_dir = QFileDialog.getExistingDirectory(self, "Select an export directory", "", QFileDialog.ShowDirsOnly) if len(base_dir) > 0: dest_path = os.path.join(base_dir, filename) try: with open(dest_path, "w") as torrent_file: torrent_file.write(json.dumps(data)) except IOError as exc: ConfirmationDialog.show_error(self.window(), "Error exporting file", str(exc))
class TorrentDetailsTabWidget(QTabWidget): """ The TorrentDetailsTabWidget is the tab that provides details about a specific selected torrent. This information includes the generic info about the torrent and trackers. """ def __init__(self, parent): QTabWidget.__init__(self, parent) uic.loadUi(get_ui_file_path('torrent_details_container.ui'), self) self.torrent_info = None self._logger = logging.getLogger("TriberGUI") self.torrent_detail_name_label = None self.torrent_detail_infohash_label = None self.torrent_detail_category_label = None self.torrent_detail_size_label = None self.torrent_detail_health_label = None self.torrent_detail_trackers_list = None self.check_health_button = None self.copy_magnet_button = None self.is_health_checking = False self.index = QModelIndex() self.healthcheck_timer = QTimer() self.healthcheck_timer.setSingleShot(True) self.healthcheck_timer.timeout.connect(self.on_check_health_clicked) self.currentChanged.connect(self.on_tab_changed) self.rest_request1 = None self.rest_request2 = None def on_tab_changed(self, index): if index == 1 and self.torrent_info: if "trackers" in self.torrent_info: for tracker in self.torrent_info["trackers"]: item = QTreeWidgetItem(self.torrent_detail_trackers_list) item.setText(0, tracker) else: self.rest_request1 = TriblerNetworkRequest( "metadata/%s/%s" % (self.torrent_info["public_key"], self.torrent_info["id"]), self.on_torrent_info) def initialize_details_widget(self): """ Initialize the details widget. We need to manually assign these attributes since we're dynamically loading this view (using uic.loadUI). """ self.torrent_detail_name_label = self.findChild( QLabel, "torrent_detail_name_label") self.torrent_detail_infohash_label = self.findChild( QLabel, "torrent_detail_infohash_label") self.torrent_detail_category_label = self.findChild( QLabel, "torrent_detail_category_label") self.torrent_detail_size_label = self.findChild( QLabel, "torrent_detail_size_label") self.torrent_detail_health_label = self.findChild( QLabel, "torrent_detail_health_label") self.torrent_detail_trackers_list = self.findChild( QTreeWidget, "torrent_detail_trackers_list") self.setCurrentIndex(0) self.check_health_button = self.findChild(EllipseButton, "check_health_button") self.check_health_button.clicked.connect(self.on_check_health_clicked) self.copy_magnet_button = self.findChild(QToolButton, "copy_magnet_button") self.copy_magnet_button.clicked.connect(self.on_copy_magnet_clicked) def on_torrent_info(self, torrent_info): # TODO: DRY this with on_tab_changed if not torrent_info or "infohash" not in torrent_info: return if self.torrent_info["infohash"] != torrent_info['infohash']: return self.torrent_info.update(torrent_info) for tracker in torrent_info["trackers"]: item = QTreeWidgetItem(self.torrent_detail_trackers_list) item.setText(0, tracker) def update_with_torrent(self, index, torrent_info): self.torrent_info = torrent_info self.index = index self.torrent_detail_name_label.setText(self.torrent_info["name"]) if self.torrent_info["category"]: self.torrent_detail_category_label.setText( self.torrent_info["category"].lower()) else: self.torrent_detail_category_label.setText("unknown") if self.torrent_info["size"] is None: self.torrent_detail_size_label.setText("Size: -") else: self.torrent_detail_size_label.setText( "%s" % format_size(float(self.torrent_info["size"]))) self.update_health_label(torrent_info['num_seeders'], torrent_info['num_leechers'], torrent_info['last_tracker_check']) self.torrent_detail_infohash_label.setText( self.torrent_info["infohash"]) self.setCurrentIndex(0) self.setTabEnabled(1, True) # If we do not have the health of this torrent, query it, but do it delayed. # When the user scrolls the list, we only want to trigger health checks on the line # that the user stopped on, so we do not generate excessive health checks. if self.is_health_checking: if self.rest_request1: self.rest_request1.cancel_request() if self.rest_request2: self.rest_request2.cancel_request() self.is_health_checking = False if torrent_info['last_tracker_check'] == 0: if not self.healthcheck_timer.isActive(): self.on_check_health_clicked() self.healthcheck_timer.stop() self.healthcheck_timer.start(HEALTHCHECK_DELAY) self.update_health_label(torrent_info['num_seeders'], torrent_info['num_leechers'], torrent_info['last_tracker_check']) self.torrent_detail_trackers_list.clear() def on_check_health_clicked(self): if not self.is_health_checking: self.check_torrent_health() def update_health_label(self, seeders, leechers, last_tracker_check): try: health = get_health(seeders, leechers, last_tracker_check) if health == HEALTH_UNCHECKED: self.torrent_detail_health_label.setText("Unknown health") elif health == HEALTH_GOOD: self.torrent_detail_health_label.setText( "Good health (S%d L%d)" % (seeders, leechers)) elif health == HEALTH_MOOT: self.torrent_detail_health_label.setText( "Unknown health (found peers)") else: self.torrent_detail_health_label.setText("No peers found") except RuntimeError: self._logger.error( "The underlying GUI widget has already been removed.") def check_torrent_health(self): if not self.torrent_info: return infohash = self.torrent_info[u'infohash'] self.is_health_checking = True def on_cancel_health_check(): self.is_health_checking = False if u'health' in self.index.model().column_position: # TODO: DRY this copypaste! # Check if details widget is still showing the same entry and the entry still exists in the table try: data_item = self.index.model().data_items[self.index.row()] except IndexError: return if self.torrent_info["infohash"] != data_item[u'infohash']: return data_item[u'health'] = HEALTH_CHECKING index = self.index.model().index( self.index.row(), self.index.model().column_position[u'health']) self.index.model().dataChanged.emit(index, index, []) self.torrent_detail_health_label.setText("Checking...") self.rest_request2 = TriblerNetworkRequest( "metadata/torrents/%s/health" % infohash, self.on_health_response, url_params={ "nowait": True, "refresh": True }, capture_errors=False, priority=QNetworkRequest.LowPriority, on_cancel=on_cancel_health_check, ) def on_health_response(self, response): total_seeders = 0 total_leechers = 0 if not response or 'error' in response: self.update_torrent_health( 0, 0) # Just set the health to 0 seeders, 0 leechers return if 'checking' in response: return for _, status in response['health'].items(): if 'error' in status: continue # Timeout or invalid status total_seeders += int(status['seeders']) total_leechers += int(status['leechers']) self.update_torrent_health(total_seeders, total_leechers) def update_torrent_health(self, seeders, leechers): # Check if details widget is still showing the same entry and the entry still exists in the table try: data_item = self.index.model().data_items[self.index.row()] except IndexError: return if self.torrent_info["infohash"] != data_item[u'infohash']: return data_item[u'num_seeders'] = seeders data_item[u'num_leechers'] = leechers data_item[u'last_tracker_check'] = time.time() data_item[u'health'] = get_health(data_item[u'num_seeders'], data_item[u'num_leechers'], data_item[u'last_tracker_check']) if u'health' in self.index.model().column_position: index = self.index.model().index( self.index.row(), self.index.model().column_position[u'health']) self.index.model().dataChanged.emit(index, index, []) # Update the health label of the detail widget self.update_health_label(data_item[u'num_seeders'], data_item[u'num_leechers'], data_item[u'last_tracker_check']) def on_copy_magnet_clicked(self): magnet_link = compose_magnetlink( self.torrent_info['infohash'], name=self.torrent_info.get('name', None), trackers=self.torrent_info.get('trackers', None), ) copy_to_clipboard(magnet_link) self.window().tray_show_message("Copying magnet link", magnet_link)