def on_export_download_dialog_done(action): if action == 0: dest_path = os.path.join(export_dir, dialog.dialog_widget.dialog_input.text()) request_mgr = TriblerRequestManager() request_mgr.download_file("channels/discovered/%s/mdblob" % mdblob_name, lambda data: on_export_download_request_done(dest_path, data)) dialog.close_dialog()
def on_export_download_dialog_done(action): if action == 0: dest_path = os.path.join( export_dir, dialog.dialog_widget.dialog_input.text()) request_mgr = TriblerRequestManager() request_mgr.download_file( "channels/discovered/%s/mdblob" % mdblob_name, lambda data: on_export_download_request_done( dest_path, data)) dialog.close_dialog()
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.request_mgr = None 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().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().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 def hideEvent(self, hide_event): self.stop_timer() 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.run_with_timer(self.load_dht_tab) 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) 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) 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): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("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("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 endpoint, method, data, timestamp, status_code in sorted(tribler_performed_requests, key=lambda x: x[3]): item = QTreeWidgetItem(self.window().requests_tree_widget) item.setText(0, "%s %s %s" % (method, endpoint, 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): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("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): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("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): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("ipv8/overlays", self.on_ipv8_community_stats) def _colored_peer_count(self, peer_count, overlay_count, master_peer): is_discovery = (master_peer == "3081a7301006072a8648ce3d020106052b81040027038192000403b3ab059ced9b20646ab5e01" "762b3595c5e8855227ae1e424cff38a1e4edee73734ff2e2e829eb4f39bab20d7578284fcba72" "51acd74e7daf96f21d01ea17077faf4d27a655837d072baeb671287a88554e1191d8904b0dc57" "2d09ff95f10ff092c8a5e2a01cd500624376aec875a6e3028aab784cfaf0bac6527245db8d939" "00d904ac2a922a02716ccef5a22f7968") limits = [20, overlay_count * 30 + 1] if is_discovery else [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["master_peer"])) 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, xrange(10)) def load_ipv8_community_details_tab(self): if self.ipv8_statistics_enabled: self.window().ipv8_statistics_error_label.setHidden(True) self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("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 add_items_to_tree(self, tree, items, keys): tree.clear() for item in items: widget_item = QTreeWidgetItem(tree) for index, key in enumerate(keys): value = format_size(item[key]) if key in ["bytes_up", "bytes_down"] else str(item[key]) widget_item.setText(index, value) tree.addTopLevelItem(widget_item) def load_tunnel_circuits_tab(self): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("ipv8/tunnel/circuits", self.on_tunnel_circuits) def on_tunnel_circuits(self, data): if data: self.add_items_to_tree(self.window().circuits_tree_widget, data.get("circuits"), ["circuit_id", "goal_hops", "actual_hops", "unverified_hop", "type", "state", "bytes_up", "bytes_down"]) def load_tunnel_relays_tab(self): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("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"]) def load_tunnel_exits_tab(self): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("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"]) def load_dht_tab(self): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("ipv8/dht/statistics", self.on_dht_statistics) def on_dht_statistics(self, data): if not data: return self.window().dht_tree_widget.clear() for key, value in data["statistics"].items(): self.create_and_add_widget_item(key, value, self.window().dht_tree_widget) 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) self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("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): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("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): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("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 = CPUPlotMplCanvas(self.window().cpu_plot_widget, dpi=100) 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): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("debug/cpu/history", self.on_core_cpu_history) def on_core_cpu_history(self, data): if not data: return plot_data = [[], []] for cpu_info in data["cpu_history"]: if cpu_info["cpu"] == 0.0: continue # Ignore the initial measurement, is always zero plot_data[0].append(datetime.datetime.fromtimestamp(cpu_info["time"])) plot_data[1].append(cpu_info["cpu"]) if len(plot_data[0]) == 0: plot_data = [[datetime.datetime.now()], [0]] self.cpu_plot.plot_data = plot_data self.cpu_plot.compute_initial_figure() def load_memory_tab(self): if not self.initialized_memory_plot: vlayout = self.window().memory_plot_widget.layout() self.memory_plot = MemoryPlotMplCanvas(self.window().memory_plot_widget, dpi=100) 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) self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("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) self.request_mgr = TriblerRequestManager() method = "DELETE" if self.profiler_enabled else "PUT" self.request_mgr.perform_request("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): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("debug/memory/history", self.on_core_memory_history) def on_core_memory_history(self, data): if not data: return plot_data = [[], []] for mem_info in data["memory_history"]: plot_data[0].append(datetime.datetime.fromtimestamp(mem_info["time"])) plot_data[1].append(mem_info["mem"] / 1024 / 1024) if len(plot_data[0]) == 0: plot_data = [[datetime.datetime.now()], [0]] self.memory_plot.plot_data = plot_data self.memory_plot.compute_initial_figure() 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.request_mgr = TriblerRequestManager() self.request_mgr.download_file("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: memory_dump_file = open(dest_path, "wb") memory_dump_file.write(data) memory_dump_file.close() 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): self.request_mgr.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" self.request_mgr = TriblerRequestManager() request_query = "process=%s&max_lines=%s" % (tab_name, max_log_lines) self.request_mgr.perform_request("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): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("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): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("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: torrent_file = open(dest_path, "wb") torrent_file.write(json.dumps(data)) torrent_file.close() except IOError as exc: ConfirmationDialog.show_error(self.window(), "Error exporting file", str(exc))
class DebugWindow(QMainWindow): """ The debug window shows various statistics about Tribler such as performed requests, Dispersy statistics and community information. """ def __init__(self, settings, tribler_version): QMainWindow.__init__(self) self.request_mgr = None 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().dispersy_tab_widget.setCurrentIndex(0) self.window().system_tab_widget.setCurrentIndex(0) self.window().debug_tab_widget.currentChanged.connect(self.tab_changed) self.window().dispersy_tab_widget.currentChanged.connect( self.dispersy_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['dispersy']['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()) # 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()) def tab_changed(self, index): if index == 0: self.load_general_tab() elif index == 1: self.load_requests_tab() elif index == 2: self.load_trustchain_tab() elif index == 3: self.dispersy_tab_changed( self.window().dispersy_tab_widget.currentIndex()) elif index == 4: self.load_events_tab() elif index == 5: self.system_tab_changed( self.window().system_tab_widget.currentIndex()) elif index == 6: self.load_logs_tab() def dispersy_tab_changed(self, index): if index == 0: self.load_dispersy_general_tab() elif index == 1: self.load_dispersy_communities_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): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("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("Number of channels", data["num_channels"], self.window().general_tree_widget) self.create_and_add_widget_item("Database size", format_size(data["database_size"]), self.window().general_tree_widget) self.create_and_add_widget_item("Number of collected torrents", data["torrents"]["num_collected"], self.window().general_tree_widget) self.create_and_add_widget_item("Number of torrent files", data["torrents"]["num_files"], self.window().general_tree_widget) self.create_and_add_widget_item( "Total size of torrent files", format_size(data["torrents"]["total_size"]), 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 endpoint, method, data, timestamp, status_code in sorted( tribler_performed_requests, key=lambda x: x[3]): item = QTreeWidgetItem(self.window().requests_tree_widget) item.setText(0, "%s %s %s" % (method, endpoint, 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): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("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"].iteritems(): self.create_and_add_widget_item( key, value, self.window().trustchain_tree_widget) def load_dispersy_general_tab(self): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("statistics/dispersy", self.on_dispersy_general_stats) def on_dispersy_general_stats(self, data): if not data: return self.window().dispersy_general_tree_widget.clear() for key, value in data["dispersy_statistics"].iteritems(): self.create_and_add_widget_item( key, value, self.window().dispersy_general_tree_widget) def load_dispersy_communities_tab(self): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("statistics/communities", self.on_dispersy_community_stats) def on_dispersy_community_stats(self, data): if not data: return self.window().communities_tree_widget.clear() for community in data["dispersy_community_statistics"]: item = QTreeWidgetItem(self.window().communities_tree_widget) item.setText(0, community["classification"]) item.setText(1, community["identifier"][:6]) item.setText(2, community["member"][:6]) item.setText(3, "%s" % community["candidates"]) self.window().communities_tree_widget.addTopLevelItem(item) 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) self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("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): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("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): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("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 = CPUPlotMplCanvas(self.window().cpu_plot_widget, dpi=100) 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): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("debug/cpu/history", self.on_core_cpu_history) def on_core_cpu_history(self, data): if not data: return plot_data = [[], []] for cpu_info in data["cpu_history"]: if cpu_info["cpu"] == 0.0: continue # Ignore the initial measurement, is always zero plot_data[0].append( datetime.datetime.fromtimestamp(cpu_info["time"])) plot_data[1].append(cpu_info["cpu"]) if len(plot_data[0]) == 0: plot_data = [[datetime.datetime.now()], [0]] self.cpu_plot.plot_data = plot_data self.cpu_plot.compute_initial_figure() def load_memory_tab(self): if not self.initialized_memory_plot: vlayout = self.window().memory_plot_widget.layout() self.memory_plot = MemoryPlotMplCanvas( self.window().memory_plot_widget, dpi=100) 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) self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("debug/profiler", self.on_profiler_info) def on_profiler_info(self, data): 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) self.request_mgr = TriblerRequestManager() method = "DELETE" if self.profiler_enabled else "PUT" self.request_mgr.perform_request("debug/profiler", self.on_profiler_state_changed, method=method) def on_profiler_state_changed(self, data): 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): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("debug/memory/history", self.on_core_memory_history) def on_core_memory_history(self, data): if not data: return plot_data = [[], []] for mem_info in data["memory_history"]: plot_data[0].append( datetime.datetime.fromtimestamp(mem_info["time"])) plot_data[1].append(mem_info["mem"] / 1024 / 1024) if len(plot_data[0]) == 0: plot_data = [[datetime.datetime.now()], [0]] self.memory_plot.plot_data = plot_data self.memory_plot.compute_initial_figure() 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.request_mgr = TriblerRequestManager() self.request_mgr.download_file( "debug/memory/dump", lambda data: self.on_memory_dump_data_available( filename, data)) else: scanner.dump_all_objects( os.path.join(self.export_dir, filename)) def on_memory_dump_data_available(self, filename, data): if not data: return dest_path = os.path.join(self.export_dir, filename) try: memory_dump_file = open(dest_path, "wb") memory_dump_file.write(data) memory_dump_file.close() 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.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" self.request_mgr = TriblerRequestManager() request_query = "process=%s&max_lines=%s" % (tab_name, max_log_lines) self.request_mgr.perform_request("debug/log?%s" % request_query, self.display_logs) def display_logs(self, data): 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()
class DownloadsPage(QWidget): """ This class is responsible for managing all items on the downloads page. The downloads page shows all downloads and specific details about a download. """ received_downloads = pyqtSignal(object) def __init__(self): QWidget.__init__(self) self.export_dir = None self.filter = DOWNLOADS_FILTER_ALL self.download_widgets = {} # key: infohash, value: QTreeWidgetItem self.downloads = None self.downloads_timer = QTimer() self.downloads_timeout_timer = QTimer() self.selected_item = None self.dialog = None self.downloads_request_mgr = TriblerRequestManager() self.request_mgr = None def initialize_downloads_page(self): self.window().downloads_tab.initialize() self.window().downloads_tab.clicked_tab_button.connect(self.on_downloads_tab_button_clicked) self.window().start_download_button.clicked.connect(self.on_start_download_clicked) self.window().stop_download_button.clicked.connect(self.on_stop_download_clicked) self.window().remove_download_button.clicked.connect(self.on_remove_download_clicked) self.window().play_download_button.clicked.connect(self.on_play_download_clicked) self.window().downloads_list.itemSelectionChanged.connect(self.on_download_item_clicked) self.window().downloads_list.customContextMenuRequested.connect(self.on_right_click_item) self.window().download_details_widget.initialize_details_widget() self.window().download_details_widget.hide() self.window().downloads_filter_input.textChanged.connect(self.on_filter_text_changed) self.window().downloads_list.header().resizeSection(12, 146) if not self.window().vlc_available: self.window().play_download_button.setHidden(True) def on_filter_text_changed(self, text): self.window().downloads_list.clearSelection() self.window().download_details_widget.hide() self.update_download_visibility() def start_loading_downloads(self): self.schedule_downloads_timer(now=True) def schedule_downloads_timer(self, now=False): self.downloads_timer = QTimer() self.downloads_timer.setSingleShot(True) self.downloads_timer.timeout.connect(self.load_downloads) self.downloads_timer.start(0 if now else 1000) self.downloads_timeout_timer = QTimer() self.downloads_timeout_timer.setSingleShot(True) self.downloads_timeout_timer.timeout.connect(self.on_downloads_request_timeout) self.downloads_timeout_timer.start(16000) def on_downloads_request_timeout(self): self.downloads_request_mgr.cancel_request() self.schedule_downloads_timer() def stop_loading_downloads(self): self.downloads_timer.stop() self.downloads_timeout_timer.stop() def load_downloads(self): url = "downloads?get_pieces=1" if self.window().download_details_widget.currentIndex() == 3: url = "downloads?get_peers=1&get_pieces=1" self.downloads_request_mgr.generate_request_id() self.downloads_request_mgr.perform_request(url, self.on_received_downloads) def on_received_downloads(self, downloads): if not downloads: return # This might happen when closing Tribler total_download = 0 total_upload = 0 self.received_downloads.emit(downloads) self.downloads = downloads download_infohashes = set() for download in downloads["downloads"]: if download["infohash"] in self.download_widgets: item = self.download_widgets[download["infohash"]] else: item = DownloadWidgetItem(self.window().downloads_list) self.download_widgets[download["infohash"]] = item item.update_with_download(download) # Update video player with download info video_infohash = self.window().video_player_page.active_infohash if video_infohash != "" and download["infohash"] == video_infohash: self.window().video_player_page.update_with_download_info(download) total_download += download["speed_down"] total_upload += download["speed_up"] download_infohashes.add(download["infohash"]) if self.window().download_details_widget.current_download is not None and \ self.window().download_details_widget.current_download["infohash"] == download["infohash"]: self.window().download_details_widget.current_download = download self.window().download_details_widget.update_pages() # Check whether there are download that should be removed toremove = set() for infohash, item in self.download_widgets.iteritems(): if infohash not in download_infohashes: index = self.window().downloads_list.indexOfTopLevelItem(item) toremove.add((infohash, index)) for infohash, index in toremove: self.window().downloads_list.takeTopLevelItem(index) del self.download_widgets[infohash] if self.window().tray_icon: self.window().tray_icon.setToolTip( "Down: %s, Up: %s" % (format_speed(total_download), format_speed(total_upload))) self.update_download_visibility() self.schedule_downloads_timer() # Update the top download management button if we have a row selected if len(self.window().downloads_list.selectedItems()) > 0: self.on_download_item_clicked() def update_download_visibility(self): for i in range(self.window().downloads_list.topLevelItemCount()): item = self.window().downloads_list.topLevelItem(i) filter_match = self.window().downloads_filter_input.text().lower() in item.download_info["name"].lower() item.setHidden( not item.get_raw_download_status() in DOWNLOADS_FILTER_DEFINITION[self.filter] or not filter_match) def on_downloads_tab_button_clicked(self, button_name): if button_name == "downloads_all_button": self.filter = DOWNLOADS_FILTER_ALL elif button_name == "downloads_downloading_button": self.filter = DOWNLOADS_FILTER_DOWNLOADING elif button_name == "downloads_completed_button": self.filter = DOWNLOADS_FILTER_COMPLETED elif button_name == "downloads_active_button": self.filter = DOWNLOADS_FILTER_ACTIVE elif button_name == "downloads_inactive_button": self.filter = DOWNLOADS_FILTER_INACTIVE self.window().downloads_list.clearSelection() self.window().download_details_widget.hide() self.update_download_visibility() @staticmethod def start_download_enabled(download_widget): return download_widget.get_raw_download_status() == DLSTATUS_STOPPED @staticmethod def stop_download_enabled(download_widget): status = download_widget.get_raw_download_status() return status != DLSTATUS_STOPPED and status != DLSTATUS_STOPPED_ON_ERROR @staticmethod def force_recheck_download_enabled(download_widget): status = download_widget.get_raw_download_status() return status != DLSTATUS_METADATA and status != DLSTATUS_HASHCHECKING and status != DLSTATUS_WAITING4HASHCHECK def on_download_item_clicked(self): self.window().download_details_widget.show() if len(self.window().downloads_list.selectedItems()) == 0: self.window().play_download_button.setEnabled(False) self.window().remove_download_button.setEnabled(False) self.window().start_download_button.setEnabled(False) self.window().stop_download_button.setEnabled(False) return self.selected_item = self.window().downloads_list.selectedItems()[0] self.window().play_download_button.setEnabled(True) self.window().remove_download_button.setEnabled(True) self.window().start_download_button.setEnabled(DownloadsPage.start_download_enabled(self.selected_item)) self.window().stop_download_button.setEnabled(DownloadsPage.stop_download_enabled(self.selected_item)) self.window().download_details_widget.update_with_download(self.selected_item.download_info) def on_start_download_clicked(self): infohash = self.selected_item.download_info["infohash"] self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("downloads/%s" % infohash, self.on_download_resumed, method='PATCH', data="state=resume") def on_download_resumed(self, json_result): if json_result["modified"]: self.selected_item.download_info['status'] = "DLSTATUS_DOWNLOADING" self.selected_item.update_item() self.on_download_item_clicked() def on_stop_download_clicked(self): infohash = self.selected_item.download_info["infohash"] self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("downloads/%s" % infohash, self.on_download_stopped, method='PATCH', data="state=stop") def on_play_download_clicked(self): self.window().left_menu_button_video_player.click() self.window().video_player_page.set_torrent_infohash(self.selected_item.download_info["infohash"]) self.window().left_menu_playlist.set_loading() def on_download_stopped(self, json_result): if json_result["modified"]: self.selected_item.download_info['status'] = "DLSTATUS_STOPPED" self.selected_item.update_item() self.on_download_item_clicked() def on_remove_download_clicked(self): self.dialog = ConfirmationDialog(self, "Remove download", "Are you sure you want to remove this download?", [('remove download', BUTTON_TYPE_NORMAL), ('remove download + data', BUTTON_TYPE_NORMAL), ('cancel', BUTTON_TYPE_CONFIRM)]) self.dialog.button_clicked.connect(self.on_remove_download_dialog) self.dialog.show() def on_remove_download_dialog(self, action): if action != 2: infohash = self.selected_item.download_info["infohash"] # Reset video player if necessary before doing the actual request if self.window().video_player_page.active_infohash == infohash: self.window().video_player_page.reset_player() self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("downloads/%s" % infohash, self.on_download_removed, method='DELETE', data="remove_data=%d" % action) self.dialog.setParent(None) self.dialog = None def on_download_removed(self, json_result): if json_result["removed"]: infohash = self.selected_item.download_info["infohash"] index = self.window().downloads_list.indexOfTopLevelItem(self.selected_item) self.window().downloads_list.takeTopLevelItem(index) if infohash in self.download_widgets: # Could have been removed already through API del self.download_widgets[infohash] self.window().download_details_widget.hide() def on_force_recheck_download(self): infohash = self.selected_item.download_info["infohash"] self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("downloads/%s" % infohash, self.on_forced_recheck, method='PATCH', data='state=recheck') def on_forced_recheck(self, result): if result['modified']: self.selected_item.download_info['status'] = "DLSTATUS_HASHCHECKING" self.selected_item.update_item() self.on_download_item_clicked() def change_anonymity(self, hops): infohash = self.selected_item.download_info["infohash"] self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("downloads/%s" % infohash, lambda _: None, method='PATCH', data='anon_hops=%d' % hops) def on_explore_files(self): QDesktopServices.openUrl(QUrl.fromLocalFile(self.selected_item.download_info["destination"])) def on_export_download(self): self.export_dir = QFileDialog.getExistingDirectory(self, "Please select the destination directory", "", QFileDialog.ShowDirsOnly) if len(self.export_dir) > 0: # Show confirmation dialog where we specify the name of the file torrent_name = self.selected_item.download_info['name'] self.dialog = ConfirmationDialog(self, "Export torrent file", "Please enter the name of the torrent file:", [('SAVE', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)], show_input=True) self.dialog.dialog_widget.dialog_input.setPlaceholderText('Torrent file name') self.dialog.dialog_widget.dialog_input.setText("%s.torrent" % torrent_name) self.dialog.dialog_widget.dialog_input.setFocus() self.dialog.button_clicked.connect(self.on_export_download_dialog_done) self.dialog.show() def on_export_download_dialog_done(self, action): if action == 0: filename = self.dialog.dialog_widget.dialog_input.text() self.request_mgr = TriblerRequestManager() self.request_mgr.download_file("downloads/%s/torrent" % self.selected_item.download_info['infohash'], lambda data: self.on_export_download_request_done(filename, data)) self.dialog.setParent(None) self.dialog = None def on_export_download_request_done(self, filename, data): dest_path = os.path.join(self.export_dir, filename) try: torrent_file = open(dest_path, "wb") torrent_file.write(data) torrent_file.close() except IOError as exc: ConfirmationDialog.show_error(self.window(), "Error when exporting file", "An error occurred when exporting the torrent file: %s" % str(exc)) else: if self.window().tray_icon: self.window().tray_icon.showMessage("Torrent file exported", "Torrent file exported to %s" % dest_path) def on_right_click_item(self, pos): item_clicked = self.window().downloads_list.itemAt(pos) if not item_clicked: return self.selected_item = item_clicked menu = TriblerActionMenu(self) start_action = QAction('Start', self) stop_action = QAction('Stop', self) remove_download_action = QAction('Remove download', self) force_recheck_action = QAction('Force recheck', self) export_download_action = QAction('Export .torrent file', self) explore_files_action = QAction('Explore files', self) no_anon_action = QAction('No anonymity', self) one_hop_anon_action = QAction('One hop', self) two_hop_anon_action = QAction('Two hops', self) three_hop_anon_action = QAction('Three hops', self) start_action.triggered.connect(self.on_start_download_clicked) start_action.setEnabled(DownloadsPage.start_download_enabled(self.selected_item)) stop_action.triggered.connect(self.on_stop_download_clicked) stop_action.setEnabled(DownloadsPage.stop_download_enabled(self.selected_item)) remove_download_action.triggered.connect(self.on_remove_download_clicked) force_recheck_action.triggered.connect(self.on_force_recheck_download) force_recheck_action.setEnabled(DownloadsPage.force_recheck_download_enabled(self.selected_item)) export_download_action.triggered.connect(self.on_export_download) explore_files_action.triggered.connect(self.on_explore_files) no_anon_action.triggered.connect(lambda: self.change_anonymity(0)) one_hop_anon_action.triggered.connect(lambda: self.change_anonymity(1)) two_hop_anon_action.triggered.connect(lambda: self.change_anonymity(2)) three_hop_anon_action.triggered.connect(lambda: self.change_anonymity(3)) menu.addAction(start_action) menu.addAction(stop_action) if self.window().vlc_available: play_action = QAction('Play', self) play_action.triggered.connect(self.on_play_download_clicked) menu.addAction(play_action) menu.addSeparator() menu.addAction(remove_download_action) menu.addSeparator() menu.addAction(force_recheck_action) menu.addSeparator() menu.addAction(export_download_action) menu.addSeparator() menu_anon_level = menu.addMenu("Change anonymity") menu_anon_level.addAction(no_anon_action) menu_anon_level.addAction(one_hop_anon_action) menu_anon_level.addAction(two_hop_anon_action) menu_anon_level.addAction(three_hop_anon_action) menu.addAction(explore_files_action) menu.exec_(self.window().downloads_list.mapToGlobal(pos))
class DownloadsPage(QWidget): """ This class is responsible for managing all items on the downloads page. The downloads page shows all downloads and specific details about a download. """ received_downloads = pyqtSignal(object) def __init__(self): QWidget.__init__(self) self.export_dir = None self.filter = DOWNLOADS_FILTER_ALL self.download_widgets = {} # key: infohash, value: QTreeWidgetItem self.downloads = None self.downloads_timer = QTimer() self.downloads_timeout_timer = QTimer() self.downloads_last_update = 0 self.selected_items = None self.dialog = None self.downloads_request_mgr = TriblerRequestManager() self.request_mgr = None self.loading_message_widget = None def showEvent(self, QShowEvent): """ When the downloads tab is clicked, we want to update the downloads list immediately. """ super(DownloadsPage, self).showEvent(QShowEvent) self.stop_loading_downloads() self.schedule_downloads_timer(True) def initialize_downloads_page(self): self.window().downloads_tab.initialize() self.window().downloads_tab.clicked_tab_button.connect( self.on_downloads_tab_button_clicked) self.window().start_download_button.clicked.connect( self.on_start_download_clicked) self.window().stop_download_button.clicked.connect( self.on_stop_download_clicked) self.window().remove_download_button.clicked.connect( self.on_remove_download_clicked) self.window().play_download_button.clicked.connect( self.on_play_download_clicked) self.window().downloads_list.itemSelectionChanged.connect( self.on_download_item_clicked) self.window().downloads_list.customContextMenuRequested.connect( self.on_right_click_item) self.window().download_details_widget.initialize_details_widget() self.window().download_details_widget.hide() self.window().downloads_filter_input.textChanged.connect( self.on_filter_text_changed) self.window().downloads_list.header().resizeSection(12, 146) if not self.window().vlc_available: self.window().play_download_button.setHidden(True) def tray_set_tooltip(self, message): """ Set a tooltip message for the tray icon, if possible. :param message: the message to display on hover """ if self.window().tray_icon: try: self.window().tray_icon.setToolTip(message) except RuntimeError as e: logging.error("Failed to set tray tooltip: %s", str(e)) def tray_show_message(self, title, message): """ Show a message at the tray icon, if possible. :param title: the title of the message :param message: the message to display """ if self.window().tray_icon: try: self.window().tray_icon.showMessage(title, message) except RuntimeError as e: logging.error("Failed to set tray message: %s", str(e)) def on_filter_text_changed(self, text): self.window().downloads_list.clearSelection() self.window().download_details_widget.hide() self.update_download_visibility() def start_loading_downloads(self): self.window().downloads_list.setSelectionMode( QAbstractItemView.NoSelection) self.loading_message_widget = QTreeWidgetItem() self.window().downloads_list.addTopLevelItem( self.loading_message_widget) self.window().downloads_list.setItemWidget( self.loading_message_widget, 2, LoadingListItem(self.window().downloads_list)) self.schedule_downloads_timer(now=True) def schedule_downloads_timer(self, now=False): self.downloads_timer = QTimer() self.downloads_timer.setSingleShot(True) self.downloads_timer.timeout.connect(self.load_downloads) self.downloads_timer.start(0 if now else 1000) self.downloads_timeout_timer = QTimer() self.downloads_timeout_timer.setSingleShot(True) self.downloads_timeout_timer.timeout.connect( self.on_downloads_request_timeout) self.downloads_timeout_timer.start(16000) def on_downloads_request_timeout(self): self.downloads_request_mgr.cancel_request() self.schedule_downloads_timer() def stop_loading_downloads(self): self.downloads_timer.stop() self.downloads_timeout_timer.stop() def load_downloads(self): url = "downloads?get_pieces=1" if self.window().download_details_widget.currentIndex() == 3: url += "&get_peers=1" elif self.window().download_details_widget.currentIndex() == 1: url += "&get_files=1" if not self.isHidden() or (time.time() - self.downloads_last_update > 30): # Update if the downloads page is visible or if we haven't updated for longer than 30 seconds self.downloads_last_update = time.time() priority = "LOW" if self.isHidden() else "HIGH" self.downloads_request_mgr.cancel_request() self.downloads_request_mgr = TriblerRequestManager() self.downloads_request_mgr.perform_request( url, self.on_received_downloads, priority=priority) def on_received_downloads(self, downloads): if not downloads: return # This might happen when closing Tribler loading_widget_index = self.window( ).downloads_list.indexOfTopLevelItem(self.loading_message_widget) if loading_widget_index > -1: self.window().downloads_list.takeTopLevelItem(loading_widget_index) self.window().downloads_list.setSelectionMode( QAbstractItemView.ExtendedSelection) self.downloads = downloads self.total_download = 0 self.total_upload = 0 download_infohashes = set() items = [] for download in downloads["downloads"]: if download["infohash"] in self.download_widgets: item = self.download_widgets[download["infohash"]] else: item = DownloadWidgetItem() self.download_widgets[download["infohash"]] = item items.append(item) item.update_with_download(download) # Update video player with download info video_infohash = self.window().video_player_page.active_infohash if video_infohash != "" and download["infohash"] == video_infohash: self.window().video_player_page.update_with_download_info( download) self.total_download += download["speed_down"] self.total_upload += download["speed_up"] download_infohashes.add(download["infohash"]) if self.window().download_details_widget.current_download is not None and \ self.window().download_details_widget.current_download["infohash"] == download["infohash"]: self.window( ).download_details_widget.current_download = download self.window().download_details_widget.update_pages() self.window().downloads_list.addTopLevelItems(items) for item in items: self.window().downloads_list.setItemWidget(item, 2, item.bar_container) # Check whether there are download that should be removed for infohash, item in self.download_widgets.items(): if infohash not in download_infohashes: index = self.window().downloads_list.indexOfTopLevelItem(item) self.window().downloads_list.takeTopLevelItem(index) del self.download_widgets[infohash] self.tray_set_tooltip("Down: %s, Up: %s" % (format_speed( self.total_download), format_speed(self.total_upload))) self.update_download_visibility() self.schedule_downloads_timer() # Update the top download management button if we have a row selected if len(self.window().downloads_list.selectedItems()) > 0: self.on_download_item_clicked() self.update_credit_mining_disk_usage() self.received_downloads.emit(downloads) def update_download_visibility(self): for i in range(self.window().downloads_list.topLevelItemCount()): item = self.window().downloads_list.topLevelItem(i) filter_match = self.window().downloads_filter_input.text().lower( ) in item.download_info["name"].lower() is_creditmining = item.download_info["credit_mining"] if self.filter == DOWNLOADS_FILTER_CREDITMINING: item.setHidden(not is_creditmining or not filter_match) else: item.setHidden(not item.get_raw_download_status() in DOWNLOADS_FILTER_DEFINITION[self.filter] or \ not filter_match or is_creditmining) def on_downloads_tab_button_clicked(self, button_name): if button_name == "downloads_all_button": self.filter = DOWNLOADS_FILTER_ALL elif button_name == "downloads_downloading_button": self.filter = DOWNLOADS_FILTER_DOWNLOADING elif button_name == "downloads_completed_button": self.filter = DOWNLOADS_FILTER_COMPLETED elif button_name == "downloads_active_button": self.filter = DOWNLOADS_FILTER_ACTIVE elif button_name == "downloads_inactive_button": self.filter = DOWNLOADS_FILTER_INACTIVE elif button_name == "downloads_creditmining_button": self.filter = DOWNLOADS_FILTER_CREDITMINING self.window().downloads_list.clearSelection() self.window().download_details_widget.hide() self.update_download_visibility() self.update_credit_mining_disk_usage() def update_credit_mining_disk_usage(self): on_credit_mining_tab = self.filter == DOWNLOADS_FILTER_CREDITMINING self.window().diskspace_usage.setVisible(on_credit_mining_tab) if not on_credit_mining_tab: return bytes_max = self.window( ).tribler_settings["credit_mining"]["max_disk_space"] bytes_used = 0 for download in self.downloads["downloads"]: if download["credit_mining"] and \ download["status"] in ("DLSTATUS_DOWNLOADING", "DLSTATUS_SEEDING", "DLSTATUS_STOPPED", "DLSTATUS_STOPPED_ON_ERROR"): bytes_used += download["progress"] * download["size"] self.window().diskspace_usage.setText( "Current disk space usage %s / %s" % (format_size(float(bytes_used)), format_size(float(bytes_max)))) @staticmethod def start_download_enabled(download_widgets): return any([ download_widget.get_raw_download_status() == DLSTATUS_STOPPED for download_widget in download_widgets ]) @staticmethod def stop_download_enabled(download_widgets): return any([ download_widget.get_raw_download_status() not in [DLSTATUS_STOPPED, DLSTATUS_STOPPED_ON_ERROR] for download_widget in download_widgets ]) @staticmethod def force_recheck_download_enabled(download_widgets): return any([ download_widget.get_raw_download_status() not in [ DLSTATUS_METADATA, DLSTATUS_HASHCHECKING, DLSTATUS_WAITING4HASHCHECK ] for download_widget in download_widgets ]) def on_download_item_clicked(self): selected_count = len(self.window().downloads_list.selectedItems()) if selected_count == 0: self.window().play_download_button.setEnabled(False) self.window().remove_download_button.setEnabled(False) self.window().start_download_button.setEnabled(False) self.window().stop_download_button.setEnabled(False) self.window().download_details_widget.hide() elif selected_count == 1: self.selected_items = self.window().downloads_list.selectedItems() self.window().play_download_button.setEnabled(True) self.window().remove_download_button.setEnabled(True) self.window().start_download_button.setEnabled( DownloadsPage.start_download_enabled(self.selected_items)) self.window().stop_download_button.setEnabled( DownloadsPage.stop_download_enabled(self.selected_items)) self.window().download_details_widget.update_with_download( self.selected_items[0].download_info) self.window().download_details_widget.show() else: self.selected_items = self.window().downloads_list.selectedItems() self.window().play_download_button.setEnabled(False) self.window().remove_download_button.setEnabled(True) self.window().start_download_button.setEnabled( DownloadsPage.start_download_enabled(self.selected_items)) self.window().stop_download_button.setEnabled( DownloadsPage.stop_download_enabled(self.selected_items)) self.window().download_details_widget.hide() def on_start_download_clicked(self): for selected_item in self.selected_items: infohash = selected_item.download_info["infohash"] self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("downloads/%s" % infohash, self.on_download_resumed, method='PATCH', data="state=resume") def on_download_resumed(self, json_result): if json_result and 'modified' in json_result: for selected_item in self.selected_items: if selected_item.download_info["infohash"] == json_result[ "infohash"]: selected_item.download_info[ 'status'] = "DLSTATUS_DOWNLOADING" selected_item.update_item() self.on_download_item_clicked() def on_stop_download_clicked(self): for selected_item in self.selected_items: infohash = selected_item.download_info["infohash"] self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("downloads/%s" % infohash, self.on_download_stopped, method='PATCH', data="state=stop") def on_play_download_clicked(self): self.window().left_menu_button_video_player.click() selected_item = self.selected_items[:1] if selected_item and \ self.window().video_player_page.active_infohash != selected_item[0].download_info["infohash"]: self.window().video_player_page.play_media_item( selected_item[0].download_info["infohash"], -1) def on_download_stopped(self, json_result): if json_result and "modified" in json_result: for selected_item in self.selected_items: if selected_item.download_info["infohash"] == json_result[ "infohash"]: selected_item.download_info['status'] = "DLSTATUS_STOPPED" selected_item.update_item() self.on_download_item_clicked() def on_remove_download_clicked(self): self.dialog = ConfirmationDialog( self, "Remove download", "Are you sure you want to remove this download?", [('remove download', BUTTON_TYPE_NORMAL), ('remove download + data', BUTTON_TYPE_NORMAL), ('cancel', BUTTON_TYPE_CONFIRM)]) self.dialog.button_clicked.connect(self.on_remove_download_dialog) self.dialog.show() def on_remove_download_dialog(self, action): if action != 2: for selected_item in self.selected_items: infohash = selected_item.download_info["infohash"] # Reset video player if necessary before doing the actual request if self.window().video_player_page.active_infohash == infohash: self.window().video_player_page.reset_player() self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("downloads/%s" % infohash, self.on_download_removed, method='DELETE', data="remove_data=%d" % action) self.dialog.close_dialog() self.dialog = None def on_download_removed(self, json_result): if json_result and "removed" in json_result: self.load_downloads() self.window().download_details_widget.hide() def on_force_recheck_download(self): for selected_item in self.selected_items: infohash = selected_item.download_info["infohash"] self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("downloads/%s" % infohash, self.on_forced_recheck, method='PATCH', data='state=recheck') def on_forced_recheck(self, result): if result and "modified" in result: for selected_item in self.selected_items: if selected_item.download_info["infohash"] == result[ "infohash"]: selected_item.download_info[ 'status'] = "DLSTATUS_HASHCHECKING" selected_item.update_item() self.on_download_item_clicked() def change_anonymity(self, hops): for selected_item in self.selected_items: infohash = selected_item.download_info["infohash"] self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("downloads/%s" % infohash, lambda _: None, method='PATCH', data='anon_hops=%d' % hops) def on_explore_files(self): for selected_item in self.selected_items: path = os.path.normpath( os.path.join( self.window().tribler_settings['download_defaults'] ['saveas'], selected_item.download_info["destination"])) QDesktopServices.openUrl(QUrl.fromLocalFile(path)) def on_export_download(self): self.export_dir = QFileDialog.getExistingDirectory( self, "Please select the destination directory", "", QFileDialog.ShowDirsOnly) selected_item = self.selected_items[:1] if len(self.export_dir) > 0 and selected_item: # Show confirmation dialog where we specify the name of the file torrent_name = selected_item[0].download_info['name'] self.dialog = ConfirmationDialog( self, "Export torrent file", "Please enter the name of the torrent file:", [('SAVE', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)], show_input=True) self.dialog.dialog_widget.dialog_input.setPlaceholderText( 'Torrent file name') self.dialog.dialog_widget.dialog_input.setText("%s.torrent" % torrent_name) self.dialog.dialog_widget.dialog_input.setFocus() self.dialog.button_clicked.connect( self.on_export_download_dialog_done) self.dialog.show() def on_export_download_dialog_done(self, action): selected_item = self.selected_items[:1] if action == 0 and selected_item: filename = self.dialog.dialog_widget.dialog_input.text() self.request_mgr = TriblerRequestManager() self.request_mgr.download_file( "downloads/%s/torrent" % selected_item[0].download_info['infohash'], lambda data: self.on_export_download_request_done( filename, data)) self.dialog.close_dialog() self.dialog = None def on_export_download_request_done(self, filename, data): dest_path = os.path.join(self.export_dir, filename) try: torrent_file = open(dest_path, "wb") torrent_file.write(data) torrent_file.close() except IOError as exc: ConfirmationDialog.show_error( self.window(), "Error when exporting file", "An error occurred when exporting the torrent file: %s" % str(exc)) else: self.tray_show_message("Torrent file exported", "Torrent file exported to %s" % dest_path) def on_right_click_item(self, pos): item_clicked = self.window().downloads_list.itemAt(pos) if not item_clicked or self.selected_items is None: return if item_clicked not in self.selected_items: self.selected_items.append(item_clicked) menu = TriblerActionMenu(self) start_action = QAction('Start', self) stop_action = QAction('Stop', self) remove_download_action = QAction('Remove download', self) force_recheck_action = QAction('Force recheck', self) export_download_action = QAction('Export .torrent file', self) explore_files_action = QAction('Explore files', self) no_anon_action = QAction('No anonymity', self) one_hop_anon_action = QAction('One hop', self) two_hop_anon_action = QAction('Two hops', self) three_hop_anon_action = QAction('Three hops', self) start_action.triggered.connect(self.on_start_download_clicked) start_action.setEnabled( DownloadsPage.start_download_enabled(self.selected_items)) stop_action.triggered.connect(self.on_stop_download_clicked) stop_action.setEnabled( DownloadsPage.stop_download_enabled(self.selected_items)) remove_download_action.triggered.connect( self.on_remove_download_clicked) force_recheck_action.triggered.connect(self.on_force_recheck_download) force_recheck_action.setEnabled( DownloadsPage.force_recheck_download_enabled(self.selected_items)) export_download_action.triggered.connect(self.on_export_download) explore_files_action.triggered.connect(self.on_explore_files) no_anon_action.triggered.connect(lambda: self.change_anonymity(0)) one_hop_anon_action.triggered.connect(lambda: self.change_anonymity(1)) two_hop_anon_action.triggered.connect(lambda: self.change_anonymity(2)) three_hop_anon_action.triggered.connect( lambda: self.change_anonymity(3)) menu.addAction(start_action) menu.addAction(stop_action) if self.window().vlc_available and len(self.selected_items) == 1: play_action = QAction('Play', self) play_action.triggered.connect(self.on_play_download_clicked) menu.addAction(play_action) menu.addSeparator() menu.addAction(remove_download_action) menu.addSeparator() menu.addAction(force_recheck_action) menu.addSeparator() menu.addAction(export_download_action) menu.addSeparator() menu_anon_level = menu.addMenu("Change anonymity") menu_anon_level.addAction(no_anon_action) menu_anon_level.addAction(one_hop_anon_action) menu_anon_level.addAction(two_hop_anon_action) menu_anon_level.addAction(three_hop_anon_action) menu.addAction(explore_files_action) menu.exec_(self.window().downloads_list.mapToGlobal(pos))
class DownloadsPage(QWidget): """ This class is responsible for managing all items on the downloads page. The downloads page shows all downloads and specific details about a download. """ received_downloads = pyqtSignal(object) def __init__(self): QWidget.__init__(self) self.export_dir = None self.filter = DOWNLOADS_FILTER_ALL self.download_widgets = {} # key: infohash, value: QTreeWidgetItem self.downloads = None self.downloads_timer = QTimer() self.downloads_timeout_timer = QTimer() self.downloads_last_update = 0 self.selected_items = None self.dialog = None self.downloads_request_mgr = TriblerRequestManager() self.request_mgr = None self.loading_message_widget = None def showEvent(self, QShowEvent): """ When the downloads tab is clicked, we want to update the downloads list immediately. """ super(DownloadsPage, self).showEvent(QShowEvent) self.stop_loading_downloads() self.schedule_downloads_timer(True) def initialize_downloads_page(self): self.window().downloads_tab.initialize() self.window().downloads_tab.clicked_tab_button.connect(self.on_downloads_tab_button_clicked) self.window().start_download_button.clicked.connect(self.on_start_download_clicked) self.window().stop_download_button.clicked.connect(self.on_stop_download_clicked) self.window().remove_download_button.clicked.connect(self.on_remove_download_clicked) self.window().play_download_button.clicked.connect(self.on_play_download_clicked) self.window().downloads_list.itemSelectionChanged.connect(self.on_download_item_clicked) self.window().downloads_list.customContextMenuRequested.connect(self.on_right_click_item) self.window().download_details_widget.initialize_details_widget() self.window().download_details_widget.hide() self.window().downloads_filter_input.textChanged.connect(self.on_filter_text_changed) self.window().downloads_list.header().resizeSection(12, 146) if not self.window().vlc_available: self.window().play_download_button.setHidden(True) def on_filter_text_changed(self, text): self.window().downloads_list.clearSelection() self.window().download_details_widget.hide() self.update_download_visibility() def start_loading_downloads(self): self.window().downloads_list.setSelectionMode(QAbstractItemView.NoSelection) self.loading_message_widget = QTreeWidgetItem() self.window().downloads_list.addTopLevelItem(self.loading_message_widget) self.window().downloads_list.setItemWidget(self.loading_message_widget, 2, LoadingListItem(self.window().downloads_list)) self.schedule_downloads_timer(now=True) def schedule_downloads_timer(self, now=False): self.downloads_timer = QTimer() self.downloads_timer.setSingleShot(True) self.downloads_timer.timeout.connect(self.load_downloads) self.downloads_timer.start(0 if now else 1000) self.downloads_timeout_timer = QTimer() self.downloads_timeout_timer.setSingleShot(True) self.downloads_timeout_timer.timeout.connect(self.on_downloads_request_timeout) self.downloads_timeout_timer.start(16000) def on_downloads_request_timeout(self): self.downloads_request_mgr.cancel_request() self.schedule_downloads_timer() def stop_loading_downloads(self): self.downloads_timer.stop() self.downloads_timeout_timer.stop() def load_downloads(self): url = "downloads?get_pieces=1" if self.window().download_details_widget.currentIndex() == 3: url += "&get_peers=1" elif self.window().download_details_widget.currentIndex() == 1: url += "&get_files=1" if not self.isHidden() or (time.time() - self.downloads_last_update > 30): # Update if the downloads page is visible or if we haven't updated for longer than 30 seconds self.downloads_last_update = time.time() priority = "LOW" if self.isHidden() else "HIGH" self.downloads_request_mgr.cancel_request() self.downloads_request_mgr = TriblerRequestManager() self.downloads_request_mgr.perform_request(url, self.on_received_downloads, priority=priority) def on_received_downloads(self, downloads): if not downloads: return # This might happen when closing Tribler loading_widget_index = self.window().downloads_list.indexOfTopLevelItem(self.loading_message_widget) if loading_widget_index > -1: self.window().downloads_list.takeTopLevelItem(loading_widget_index) self.window().downloads_list.setSelectionMode(QAbstractItemView.ExtendedSelection) self.downloads = downloads self.total_download = 0 self.total_upload = 0 download_infohashes = set() items = [] for download in downloads["downloads"]: if download["infohash"] in self.download_widgets: item = self.download_widgets[download["infohash"]] else: item = DownloadWidgetItem() self.download_widgets[download["infohash"]] = item items.append(item) item.update_with_download(download) # Update video player with download info video_infohash = self.window().video_player_page.active_infohash if video_infohash != "" and download["infohash"] == video_infohash: self.window().video_player_page.update_with_download_info(download) self.total_download += download["speed_down"] self.total_upload += download["speed_up"] download_infohashes.add(download["infohash"]) if self.window().download_details_widget.current_download is not None and \ self.window().download_details_widget.current_download["infohash"] == download["infohash"]: self.window().download_details_widget.current_download = download self.window().download_details_widget.update_pages() self.window().downloads_list.addTopLevelItems(items) for item in items: self.window().downloads_list.setItemWidget(item, 2, item.bar_container) # Check whether there are download that should be removed for infohash, item in self.download_widgets.items(): if infohash not in download_infohashes: index = self.window().downloads_list.indexOfTopLevelItem(item) self.window().downloads_list.takeTopLevelItem(index) del self.download_widgets[infohash] self.window().tray_set_tooltip("Down: %s, Up: %s" % (format_speed(self.total_download), format_speed(self.total_upload))) self.update_download_visibility() self.schedule_downloads_timer() # Update the top download management button if we have a row selected if len(self.window().downloads_list.selectedItems()) > 0: self.on_download_item_clicked() self.update_credit_mining_disk_usage() self.received_downloads.emit(downloads) def update_download_visibility(self): for i in range(self.window().downloads_list.topLevelItemCount()): item = self.window().downloads_list.topLevelItem(i) if not isinstance(item, DownloadWidgetItem): continue filter_match = self.window().downloads_filter_input.text().lower() in item.download_info["name"].lower() is_creditmining = item.download_info["credit_mining"] if self.filter == DOWNLOADS_FILTER_CREDITMINING: item.setHidden(not is_creditmining or not filter_match) else: item.setHidden(not item.get_raw_download_status() in DOWNLOADS_FILTER_DEFINITION[self.filter] or \ not filter_match or is_creditmining) def on_downloads_tab_button_clicked(self, button_name): if button_name == "downloads_all_button": self.filter = DOWNLOADS_FILTER_ALL elif button_name == "downloads_downloading_button": self.filter = DOWNLOADS_FILTER_DOWNLOADING elif button_name == "downloads_completed_button": self.filter = DOWNLOADS_FILTER_COMPLETED elif button_name == "downloads_active_button": self.filter = DOWNLOADS_FILTER_ACTIVE elif button_name == "downloads_inactive_button": self.filter = DOWNLOADS_FILTER_INACTIVE elif button_name == "downloads_creditmining_button": self.filter = DOWNLOADS_FILTER_CREDITMINING self.window().downloads_list.clearSelection() self.window().download_details_widget.hide() self.update_download_visibility() self.update_credit_mining_disk_usage() def update_credit_mining_disk_usage(self): on_credit_mining_tab = self.filter == DOWNLOADS_FILTER_CREDITMINING self.window().diskspace_usage.setVisible(on_credit_mining_tab) if not on_credit_mining_tab or not self.window().tribler_settings or not self.downloads: return bytes_max = self.window().tribler_settings["credit_mining"]["max_disk_space"] bytes_used = 0 for download in self.downloads["downloads"]: if download["credit_mining"] and \ download["status"] in ("DLSTATUS_DOWNLOADING", "DLSTATUS_SEEDING", "DLSTATUS_STOPPED", "DLSTATUS_STOPPED_ON_ERROR"): bytes_used += download["progress"] * download["size"] self.window().diskspace_usage.setText("Current disk space usage %s / %s" % (format_size(float(bytes_used)), format_size(float(bytes_max)))) @staticmethod def start_download_enabled(download_widgets): return any([download_widget.get_raw_download_status() == DLSTATUS_STOPPED for download_widget in download_widgets]) @staticmethod def stop_download_enabled(download_widgets): return any([download_widget.get_raw_download_status() not in [DLSTATUS_STOPPED, DLSTATUS_STOPPED_ON_ERROR] for download_widget in download_widgets]) @staticmethod def force_recheck_download_enabled(download_widgets): return any([download_widget.get_raw_download_status() not in [DLSTATUS_METADATA, DLSTATUS_HASHCHECKING, DLSTATUS_WAITING4HASHCHECK] for download_widget in download_widgets]) def on_download_item_clicked(self): selected_count = len(self.window().downloads_list.selectedItems()) if selected_count == 0: self.window().play_download_button.setEnabled(False) self.window().remove_download_button.setEnabled(False) self.window().start_download_button.setEnabled(False) self.window().stop_download_button.setEnabled(False) self.window().download_details_widget.hide() elif selected_count == 1: self.selected_items = self.window().downloads_list.selectedItems() self.window().play_download_button.setEnabled(True) self.window().remove_download_button.setEnabled(True) self.window().start_download_button.setEnabled(DownloadsPage.start_download_enabled(self.selected_items)) self.window().stop_download_button.setEnabled(DownloadsPage.stop_download_enabled(self.selected_items)) self.window().download_details_widget.update_with_download(self.selected_items[0].download_info) self.window().download_details_widget.show() else: self.selected_items = self.window().downloads_list.selectedItems() self.window().play_download_button.setEnabled(False) self.window().remove_download_button.setEnabled(True) self.window().start_download_button.setEnabled(DownloadsPage.start_download_enabled(self.selected_items)) self.window().stop_download_button.setEnabled(DownloadsPage.stop_download_enabled(self.selected_items)) self.window().download_details_widget.hide() def on_start_download_clicked(self): for selected_item in self.selected_items: infohash = selected_item.download_info["infohash"] self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("downloads/%s" % infohash, self.on_download_resumed, method='PATCH', data="state=resume") def on_download_resumed(self, json_result): if json_result and 'modified' in json_result: for selected_item in self.selected_items: if selected_item.download_info["infohash"] == json_result["infohash"]: selected_item.download_info['status'] = "DLSTATUS_DOWNLOADING" selected_item.update_item() self.on_download_item_clicked() def on_stop_download_clicked(self): for selected_item in self.selected_items: infohash = selected_item.download_info["infohash"] self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("downloads/%s" % infohash, self.on_download_stopped, method='PATCH', data="state=stop") def on_play_download_clicked(self): self.window().left_menu_button_video_player.click() selected_item = self.selected_items[:1] if selected_item and \ self.window().video_player_page.active_infohash != selected_item[0].download_info["infohash"]: self.window().video_player_page.play_media_item(selected_item[0].download_info["infohash"], -1) def on_download_stopped(self, json_result): if json_result and "modified" in json_result: for selected_item in self.selected_items: if selected_item.download_info["infohash"] == json_result["infohash"]: selected_item.download_info['status'] = "DLSTATUS_STOPPED" selected_item.update_item() self.on_download_item_clicked() def on_remove_download_clicked(self): self.dialog = ConfirmationDialog(self, "Remove download", "Are you sure you want to remove this download?", [('remove download', BUTTON_TYPE_NORMAL), ('remove download + data', BUTTON_TYPE_NORMAL), ('cancel', BUTTON_TYPE_CONFIRM)]) self.dialog.button_clicked.connect(self.on_remove_download_dialog) self.dialog.show() def on_remove_download_dialog(self, action): if action != 2: for selected_item in self.selected_items: infohash = selected_item.download_info["infohash"] # Reset video player if necessary before doing the actual request if self.window().video_player_page.active_infohash == infohash: self.window().video_player_page.reset_player() self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("downloads/%s" % infohash, self.on_download_removed, method='DELETE', data="remove_data=%d" % action) self.dialog.close_dialog() self.dialog = None def on_download_removed(self, json_result): if json_result and "removed" in json_result: self.load_downloads() self.window().download_details_widget.hide() def on_force_recheck_download(self): for selected_item in self.selected_items: infohash = selected_item.download_info["infohash"] self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("downloads/%s" % infohash, self.on_forced_recheck, method='PATCH', data='state=recheck') def on_forced_recheck(self, result): if result and "modified" in result: for selected_item in self.selected_items: if selected_item.download_info["infohash"] == result["infohash"]: selected_item.download_info['status'] = "DLSTATUS_HASHCHECKING" selected_item.update_item() self.on_download_item_clicked() def change_anonymity(self, hops): for selected_item in self.selected_items: infohash = selected_item.download_info["infohash"] self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("downloads/%s" % infohash, lambda _: None, method='PATCH', data='anon_hops=%d' % hops) def on_explore_files(self): for selected_item in self.selected_items: path = os.path.normpath(os.path.join(self.window().tribler_settings['download_defaults']['saveas'], selected_item.download_info["destination"])) QDesktopServices.openUrl(QUrl.fromLocalFile(path)) def on_export_download(self): self.export_dir = QFileDialog.getExistingDirectory(self, "Please select the destination directory", "", QFileDialog.ShowDirsOnly) selected_item = self.selected_items[:1] if len(self.export_dir) > 0 and selected_item: # Show confirmation dialog where we specify the name of the file torrent_name = selected_item[0].download_info['name'] self.dialog = ConfirmationDialog(self, "Export torrent file", "Please enter the name of the torrent file:", [('SAVE', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)], show_input=True) self.dialog.dialog_widget.dialog_input.setPlaceholderText('Torrent file name') self.dialog.dialog_widget.dialog_input.setText("%s.torrent" % torrent_name) self.dialog.dialog_widget.dialog_input.setFocus() self.dialog.button_clicked.connect(self.on_export_download_dialog_done) self.dialog.show() def on_export_download_dialog_done(self, action): selected_item = self.selected_items[:1] if action == 0 and selected_item: filename = self.dialog.dialog_widget.dialog_input.text() self.request_mgr = TriblerRequestManager() self.request_mgr.download_file("downloads/%s/torrent" % selected_item[0].download_info['infohash'], lambda data: self.on_export_download_request_done(filename, data)) self.dialog.close_dialog() self.dialog = None def on_export_download_request_done(self, filename, data): dest_path = os.path.join(self.export_dir, filename) try: torrent_file = open(dest_path, "wb") torrent_file.write(data) torrent_file.close() except IOError as exc: ConfirmationDialog.show_error(self.window(), "Error when exporting file", "An error occurred when exporting the torrent file: %s" % str(exc)) else: self.window().tray_show_message("Torrent file exported", "Torrent file exported to %s" % dest_path) def on_right_click_item(self, pos): item_clicked = self.window().downloads_list.itemAt(pos) if not item_clicked or self.selected_items is None: return if item_clicked not in self.selected_items: self.selected_items.append(item_clicked) menu = TriblerActionMenu(self) start_action = QAction('Start', self) stop_action = QAction('Stop', self) remove_download_action = QAction('Remove download', self) force_recheck_action = QAction('Force recheck', self) export_download_action = QAction('Export .torrent file', self) explore_files_action = QAction('Explore files', self) no_anon_action = QAction('No anonymity', self) one_hop_anon_action = QAction('One hop', self) two_hop_anon_action = QAction('Two hops', self) three_hop_anon_action = QAction('Three hops', self) start_action.triggered.connect(self.on_start_download_clicked) start_action.setEnabled(DownloadsPage.start_download_enabled(self.selected_items)) stop_action.triggered.connect(self.on_stop_download_clicked) stop_action.setEnabled(DownloadsPage.stop_download_enabled(self.selected_items)) remove_download_action.triggered.connect(self.on_remove_download_clicked) force_recheck_action.triggered.connect(self.on_force_recheck_download) force_recheck_action.setEnabled(DownloadsPage.force_recheck_download_enabled(self.selected_items)) export_download_action.triggered.connect(self.on_export_download) explore_files_action.triggered.connect(self.on_explore_files) no_anon_action.triggered.connect(lambda: self.change_anonymity(0)) one_hop_anon_action.triggered.connect(lambda: self.change_anonymity(1)) two_hop_anon_action.triggered.connect(lambda: self.change_anonymity(2)) three_hop_anon_action.triggered.connect(lambda: self.change_anonymity(3)) menu.addAction(start_action) menu.addAction(stop_action) if self.window().vlc_available and len(self.selected_items) == 1: play_action = QAction('Play', self) play_action.triggered.connect(self.on_play_download_clicked) menu.addAction(play_action) menu.addSeparator() menu.addAction(remove_download_action) menu.addSeparator() menu.addAction(force_recheck_action) menu.addSeparator() exclude_states = [DLSTATUS_METADATA, DLSTATUS_CIRCUITS, DLSTATUS_HASHCHECKING, DLSTATUS_WAITING4HASHCHECK] if len(self.selected_items) == 1 and self.selected_items[0].get_raw_download_status() not in exclude_states: menu.addAction(export_download_action) menu.addSeparator() menu_anon_level = menu.addMenu("Change anonymity") menu_anon_level.addAction(no_anon_action) menu_anon_level.addAction(one_hop_anon_action) menu_anon_level.addAction(two_hop_anon_action) menu_anon_level.addAction(three_hop_anon_action) menu.addAction(explore_files_action) menu.exec_(self.window().downloads_list.mapToGlobal(pos))
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): QMainWindow.__init__(self) self.request_mgr = None 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().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().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()) 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.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.load_dht_tab() elif index == 6: 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.load_ipv8_general_tab() elif index == 1: self.load_ipv8_communities_tab() elif index == 2: self.load_ipv8_community_details_tab() def tunnel_tab_changed(self, index): if index == 0: self.load_tunnel_circuits_tab() elif index == 1: self.load_tunnel_relays_tab() elif index == 2: self.load_tunnel_exits_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): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("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("Number of channels", data["num_channels"], self.window().general_tree_widget) self.create_and_add_widget_item("Database size", format_size(data["database_size"]), self.window().general_tree_widget) self.create_and_add_widget_item("Number of collected torrents", data["torrents"]["num_collected"], self.window().general_tree_widget) self.create_and_add_widget_item("Number of torrent files", data["torrents"]["num_files"], self.window().general_tree_widget) self.create_and_add_widget_item( "Total size of torrent files", format_size(data["torrents"]["total_size"]), 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 endpoint, method, data, timestamp, status_code in sorted( tribler_performed_requests, key=lambda x: x[3]): item = QTreeWidgetItem(self.window().requests_tree_widget) item.setText(0, "%s %s %s" % (method, endpoint, 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): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("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"].iteritems(): self.create_and_add_widget_item( key, value, self.window().trustchain_tree_widget) def load_ipv8_general_tab(self): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("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"].iteritems(): if key == 'total_up' or key == 'total_down': value = "%.2f MB" % (value / (1024.0 * 1024.0)) if 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): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("statistics/communities", self.on_ipv8_community_stats) def on_ipv8_community_stats(self, data): if not data: return self.window().communities_tree_widget.clear() for overlay in data["ipv8_overlay_statistics"]: 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:]) item.setText(3, "%s" % len(overlay["peers"])) 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, xrange(10)) def load_ipv8_community_details_tab(self): if self.ipv8_statistics_enabled: self.window().ipv8_statistics_error_label.setHidden(True) self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request( "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.iteritems(): 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.iteritems(): 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 add_items_to_tree(self, tree, items, keys): tree.clear() for item in items: widget_item = QTreeWidgetItem(tree) for index, key in enumerate(keys): value = format_size(item[key]) if key in [ "bytes_up", "bytes_down" ] else str(item[key]) widget_item.setText(index, value) tree.addTopLevelItem(widget_item) def load_tunnel_circuits_tab(self): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("ipv8/tunnel/circuits", self.on_tunnel_circuits) def on_tunnel_circuits(self, data): if data: self.add_items_to_tree( self.window().circuits_tree_widget, data.get("circuits"), [ "circuit_id", "goal_hops", "actual_hops", "type", "state", "bytes_up", "bytes_down" ]) def load_tunnel_relays_tab(self): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("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" ]) def load_tunnel_exits_tab(self): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("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"]) def load_dht_tab(self): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("ipv8/dht/statistics", self.on_dht_statistics) def on_dht_statistics(self, data): if not data: return self.window().dht_tree_widget.clear() for key, value in data["statistics"].iteritems(): self.create_and_add_widget_item(key, value, self.window().dht_tree_widget) 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) self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("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): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("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): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("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 = CPUPlotMplCanvas(self.window().cpu_plot_widget, dpi=100) 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): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("debug/cpu/history", self.on_core_cpu_history) def on_core_cpu_history(self, data): if not data: return plot_data = [[], []] for cpu_info in data["cpu_history"]: if cpu_info["cpu"] == 0.0: continue # Ignore the initial measurement, is always zero plot_data[0].append( datetime.datetime.fromtimestamp(cpu_info["time"])) plot_data[1].append(cpu_info["cpu"]) if len(plot_data[0]) == 0: plot_data = [[datetime.datetime.now()], [0]] self.cpu_plot.plot_data = plot_data self.cpu_plot.compute_initial_figure() def load_memory_tab(self): if not self.initialized_memory_plot: vlayout = self.window().memory_plot_widget.layout() self.memory_plot = MemoryPlotMplCanvas( self.window().memory_plot_widget, dpi=100) 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) self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("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) self.request_mgr = TriblerRequestManager() method = "DELETE" if self.profiler_enabled else "PUT" self.request_mgr.perform_request("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): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("debug/memory/history", self.on_core_memory_history) def on_core_memory_history(self, data): if not data: return plot_data = [[], []] for mem_info in data["memory_history"]: plot_data[0].append( datetime.datetime.fromtimestamp(mem_info["time"])) plot_data[1].append(mem_info["mem"] / 1024 / 1024) if len(plot_data[0]) == 0: plot_data = [[datetime.datetime.now()], [0]] self.memory_plot.plot_data = plot_data self.memory_plot.compute_initial_figure() 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.request_mgr = TriblerRequestManager() self.request_mgr.download_file( "debug/memory/dump", lambda data: self.on_memory_dump_data_available( filename, data)) else: scanner.dump_all_objects( os.path.join(self.export_dir, filename)) def on_memory_dump_data_available(self, filename, data): if not data: return dest_path = os.path.join(self.export_dir, filename) try: memory_dump_file = open(dest_path, "wb") memory_dump_file.write(data) memory_dump_file.close() 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): self.request_mgr.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" self.request_mgr = TriblerRequestManager() request_query = "process=%s&max_lines=%s" % (tab_name, max_log_lines) self.request_mgr.perform_request("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): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request( "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"].iteritems(): 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): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request( "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"].iteritems(): 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: torrent_file = open(dest_path, "wb") torrent_file.write(json.dumps(data)) torrent_file.close() except IOError as exc: ConfirmationDialog.show_error(self.window(), "Error exporting file", str(exc))
class DownloadsPage(QWidget): """ This class is responsible for managing all items on the downloads page. The downloads page shows all downloads and specific details about a download. """ received_downloads = pyqtSignal(object) def __init__(self): QWidget.__init__(self) self.export_dir = None self.filter = DOWNLOADS_FILTER_ALL self.download_widgets = {} # key: infohash, value: QTreeWidgetItem self.downloads = None self.downloads_timer = QTimer() self.selected_item = None self.dialog = None self.request_mgr = None def initialize_downloads_page(self): self.window().downloads_tab.initialize() self.window().downloads_tab.clicked_tab_button.connect( self.on_downloads_tab_button_clicked) self.window().start_download_button.clicked.connect( self.on_start_download_clicked) self.window().stop_download_button.clicked.connect( self.on_stop_download_clicked) self.window().remove_download_button.clicked.connect( self.on_remove_download_clicked) self.window().downloads_list.itemSelectionChanged.connect( self.on_download_item_clicked) self.window().downloads_list.customContextMenuRequested.connect( self.on_right_click_item) self.window().download_details_widget.initialize_details_widget() self.window().download_details_widget.hide() self.window().downloads_filter_input.textChanged.connect( self.on_filter_text_changed) def on_filter_text_changed(self, text): self.update_download_visibility() def start_loading_downloads(self): self.load_downloads() self.downloads_timer = QTimer() self.downloads_timer.timeout.connect(self.load_downloads) self.downloads_timer.start(1000) def stop_loading_downloads(self): self.downloads_timer.stop() def load_downloads(self): url = "downloads?get_pieces=1" if self.window().download_details_widget.currentIndex() == 3: url = "downloads?get_peers=1&get_pieces=1" self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request(url, self.on_received_downloads) def on_received_downloads(self, downloads): if not downloads: return # This might happen when closing Tribler total_download = 0 total_upload = 0 self.received_downloads.emit(downloads) self.downloads = downloads download_infohashes = set() for download in downloads["downloads"]: if download["infohash"] in self.download_widgets: item = self.download_widgets[download["infohash"]] else: item = DownloadWidgetItem(self.window().downloads_list) self.download_widgets[download["infohash"]] = item item.update_with_download(download) # Update video player with download info video_infohash = self.window().video_player_page.active_infohash if video_infohash != "" and download["infohash"] == video_infohash: self.window().video_player_page.update_with_download_info( download) total_download += download["speed_down"] total_upload += download["speed_up"] download_infohashes.add(download["infohash"]) if self.window().download_details_widget.current_download is not None and \ self.window().download_details_widget.current_download["infohash"] == download["infohash"]: self.window( ).download_details_widget.current_download = download self.window().download_details_widget.update_pages() # Check whether there are download that should be removed toremove = set() for infohash, item in self.download_widgets.iteritems(): if infohash not in download_infohashes: index = self.window().downloads_list.indexOfTopLevelItem(item) toremove.add((infohash, index)) for infohash, index in toremove: self.window().downloads_list.takeTopLevelItem(index) del self.download_widgets[infohash] if QSystemTrayIcon.isSystemTrayAvailable(): self.window().tray_icon.setToolTip( "Down: %s, Up: %s" % (format_speed(total_download), format_speed(total_upload))) self.update_download_visibility() def update_download_visibility(self): for i in range(self.window().downloads_list.topLevelItemCount()): item = self.window().downloads_list.topLevelItem(i) filter_match = self.window().downloads_filter_input.text().lower( ) in item.download_info["name"].lower() item.setHidden(not item.get_raw_download_status() in DOWNLOADS_FILTER_DEFINITION[self.filter] or not filter_match) def on_downloads_tab_button_clicked(self, button_name): if button_name == "downloads_all_button": self.filter = DOWNLOADS_FILTER_ALL elif button_name == "downloads_downloading_button": self.filter = DOWNLOADS_FILTER_DOWNLOADING elif button_name == "downloads_completed_button": self.filter = DOWNLOADS_FILTER_COMPLETED elif button_name == "downloads_active_button": self.filter = DOWNLOADS_FILTER_ACTIVE elif button_name == "downloads_inactive_button": self.filter = DOWNLOADS_FILTER_INACTIVE self.window().download_details_widget.clear_data() self.update_download_visibility() @staticmethod def start_download_enabled(download_widget): return download_widget.get_raw_download_status() == DLSTATUS_STOPPED @staticmethod def stop_download_enabled(download_widget): status = download_widget.get_raw_download_status() return status != DLSTATUS_STOPPED and status != DLSTATUS_STOPPED_ON_ERROR @staticmethod def force_recheck_download_enabled(download_widget): status = download_widget.get_raw_download_status() return status != DLSTATUS_METADATA and status != DLSTATUS_HASHCHECKING and status != DLSTATUS_WAITING4HASHCHECK def on_download_item_clicked(self): self.window().download_details_widget.show() if len(self.window().downloads_list.selectedItems()) == 0: self.window().remove_download_button.setEnabled(False) self.window().start_download_button.setEnabled(False) self.window().stop_download_button.setEnabled(False) return self.selected_item = self.window().downloads_list.selectedItems()[0] self.window().remove_download_button.setEnabled(True) self.window().start_download_button.setEnabled( DownloadsPage.start_download_enabled(self.selected_item)) self.window().stop_download_button.setEnabled( DownloadsPage.stop_download_enabled(self.selected_item)) self.window().download_details_widget.update_with_download( self.selected_item.download_info) def on_start_download_clicked(self): infohash = self.selected_item.download_info["infohash"] self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("downloads/%s" % infohash, self.on_download_resumed, method='PATCH', data="state=resume") def on_download_resumed(self, json_result): if json_result["modified"]: self.selected_item.download_info['status'] = "DLSTATUS_DOWNLOADING" self.selected_item.update_item() self.on_download_item_clicked() def on_stop_download_clicked(self): infohash = self.selected_item.download_info["infohash"] self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("downloads/%s" % infohash, self.on_download_stopped, method='PATCH', data="state=stop") def on_play_download_clicked(self): self.window().left_menu_button_video_player.click() self.window().video_player_page.set_torrent_infohash( self.selected_item.download_info["infohash"]) self.window().left_menu_playlist.set_loading() def on_download_stopped(self, json_result): if json_result["modified"]: self.selected_item.download_info['status'] = "DLSTATUS_STOPPED" self.selected_item.update_item() self.on_download_item_clicked() def on_remove_download_clicked(self): self.dialog = ConfirmationDialog( self, "Remove download", "Are you sure you want to remove this download?", [('remove download', BUTTON_TYPE_NORMAL), ('remove download + data', BUTTON_TYPE_NORMAL), ('cancel', BUTTON_TYPE_CONFIRM)]) self.dialog.button_clicked.connect(self.on_remove_download_dialog) self.dialog.show() def on_remove_download_dialog(self, action): if action != 2: infohash = self.selected_item.download_info["infohash"] self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("downloads/%s" % infohash, self.on_download_removed, method='DELETE', data="remove_data=%d" % action) self.dialog.setParent(None) self.dialog = None def on_download_removed(self, json_result): if json_result["removed"]: infohash = self.selected_item.download_info["infohash"] index = self.window().downloads_list.indexOfTopLevelItem( self.selected_item) self.window().downloads_list.takeTopLevelItem(index) del self.download_widgets[infohash] if self.window().downloads_list.topLevelItemCount() == 0: self.window().download_details_widget.clear_data() # Reset video player if necessary if self.window().video_player_page.active_infohash == infohash: self.window().video_player_page.reset_player() def on_force_recheck_download(self): infohash = self.selected_item.download_info["infohash"] self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("downloads/%s" % infohash, self.on_forced_recheck, method='PATCH', data='state=recheck') def on_forced_recheck(self, result): if result['modified']: self.selected_item.download_info[ 'status'] = "DLSTATUS_HASHCHECKING" self.selected_item.update_item() self.on_download_item_clicked() def on_explore_files(self): QDesktopServices.openUrl( QUrl.fromLocalFile( self.selected_item.download_info["destination"])) def on_export_download(self): self.export_dir = QFileDialog.getExistingDirectory( self, "Please select the destination directory", "", QFileDialog.ShowDirsOnly) self.request_mgr = TriblerRequestManager() self.request_mgr.download_file( "downloads/%s/torrent" % self.selected_item.download_info['infohash'], self.on_export_download_request_done) def on_export_download_request_done(self, filename, data): dest_path = os.path.join(self.export_dir, filename) with open(dest_path, "wb") as torrent_file: torrent_file.write(data) self.window().tray_icon.showMessage( "Torrent file exported", "Torrent file exported to %s" % dest_path) def on_right_click_item(self, pos): item_clicked = self.window().downloads_list.itemAt(pos) if not item_clicked: return self.selected_item = item_clicked menu = TriblerActionMenu(self) start_action = QAction('Start', self) stop_action = QAction('Stop', self) play_action = QAction('Play', self) remove_download_action = QAction('Remove download', self) force_recheck_action = QAction('Force recheck', self) export_download_action = QAction('Export .torrent file', self) explore_files_action = QAction('Explore files', self) start_action.triggered.connect(self.on_start_download_clicked) start_action.setEnabled( DownloadsPage.start_download_enabled(self.selected_item)) stop_action.triggered.connect(self.on_stop_download_clicked) stop_action.setEnabled( DownloadsPage.stop_download_enabled(self.selected_item)) play_action.triggered.connect(self.on_play_download_clicked) remove_download_action.triggered.connect( self.on_remove_download_clicked) force_recheck_action.triggered.connect(self.on_force_recheck_download) force_recheck_action.setEnabled( DownloadsPage.force_recheck_download_enabled(self.selected_item)) export_download_action.triggered.connect(self.on_export_download) explore_files_action.triggered.connect(self.on_explore_files) menu.addAction(start_action) menu.addAction(stop_action) menu.addAction(play_action) menu.addSeparator() menu.addAction(remove_download_action) menu.addSeparator() menu.addAction(force_recheck_action) menu.addSeparator() menu.addAction(export_download_action) menu.addSeparator() menu.addAction(explore_files_action) menu.exec_(self.window().downloads_list.mapToGlobal(pos))