def create_add_torrent_menu(self, menu=None): """ Create a menu to add new torrents. Shows when users click on the tray icon or the big plus button. """ menu = menu if menu is not None else TriblerActionMenu(self) browse_files_action = QAction(tr("Import torrent from file"), self) browse_directory_action = QAction( tr("Import torrent(s) from directory"), self) add_url_action = QAction(tr("Import torrent from magnet/URL"), self) create_torrent_action = QAction(tr("Create torrent from file(s)"), self) connect(browse_files_action.triggered, self.on_add_torrent_browse_file) connect(browse_directory_action.triggered, self.on_add_torrent_browse_dir) connect(add_url_action.triggered, self.on_add_torrent_from_url) connect(create_torrent_action.triggered, self.on_create_torrent) menu.addAction(browse_files_action) menu.addAction(browse_directory_action) menu.addAction(add_url_action) menu.addSeparator() menu.addAction(create_torrent_action) return menu
def perform_files_request(self): if self.closed: return direct = not self.dialog_widget.anon_download_checkbox.isChecked() request = f"torrentinfo?uri={quote_plus_unicode(self.download_uri)}" if direct is True: request = request + f"&hops=0" self.rest_request = TriblerNetworkRequest(request, self.on_received_metainfo, capture_core_errors=False) if self.metainfo_retries <= METAINFO_MAX_RETRIES: fetch_mode = tr("directly") if direct else tr("anonymously") loading_message = tr("Loading torrent files %s...") % fetch_mode timeout_message = tr("Timeout in fetching files %s. Retrying %i/%i") % ( fetch_mode, self.metainfo_retries, METAINFO_MAX_RETRIES, ) self.dialog_widget.loading_files_label.setText( loading_message if not self.metainfo_retries else timeout_message ) self.metainfo_fetch_timer = QTimer() connect(self.metainfo_fetch_timer.timeout, self.perform_files_request) self.metainfo_fetch_timer.setSingleShot(True) self.metainfo_fetch_timer.start(METAINFO_TIMEOUT) self.metainfo_retries += 1
def on_start_download_action(self, action): if action == 1: if self.dialog and self.dialog.dialog_widget: self.window().perform_start_download_request( self.download_uri, self.dialog.dialog_widget.anon_download_checkbox.isChecked( ), self.dialog.dialog_widget.safe_seed_checkbox.isChecked(), self.dialog.dialog_widget.destination_input.currentText(), self.dialog.get_selected_files(), self.dialog.dialog_widget.files_list_view. topLevelItemCount(), add_to_channel=self.dialog.dialog_widget. add_to_channel_checkbox.isChecked(), ) else: ConfirmationDialog.show_error( self, tr("Tribler UI Error"), tr("Something went wrong. Please try again.")) logging.exception( "Error while trying to download. Either dialog or dialog.dialog_widget is None" ) if self.dialog: self.dialog.close_dialog() self.dialog = None self.start_download_dialog_active = False if action == 0: # We do this after removing the dialog since process_uri_request is blocking self.process_uri_request()
def on_channel_delete(self, channel_info): def _on_delete_action(action): if action == 0: delete_data = [{ "public_key": channel_info['public_key'], "id": channel_info['id'] }] TriblerNetworkRequest( "metadata", lambda data: self.core_manager.events_manager. node_info_updated.emit(data[0]), raw_data=json.dumps(delete_data), method='DELETE', ) if self.dialog: self.dialog.close_dialog() self.dialog = None self.dialog = ConfirmationDialog( self, tr("Delete channel"), tr("Are you sure you want to <b>delete</b> your personal channel<br/>" ) + '\"' + f"<b>{channel_info['name']}</b>" + '\"' + tr("<br/>and all its contents?"), [(tr("DELETE"), BUTTON_TYPE_NORMAL), (tr("CANCEL"), BUTTON_TYPE_CONFIRM)], ) connect(self.dialog.button_clicked, _on_delete_action) self.dialog.show()
def on_config_error_signal(self, stacktrace): self._logger.error(f"Config error: {stacktrace}") user_message = tr( "Tribler recovered from a corrupted config. Please check your settings and update if necessary." ) ConfirmationDialog.show_error(self, tr("Tribler config error"), user_message)
def item_txt(self, index, role): # ACHTUNG! Dumb workaround for some mysterious race condition try: item = self.data_items[index.row()] except IndexError: return "" column = self.columns[index.column()] column_type = self.columns_shown[index.column()] data = item.get(column.dict_key, '') # Print number of torrents in the channel for channel rows in the "size" column if (column_type == Column.SIZE and "torrents" not in self.columns and "torrents" in item and item["type"] in (CHANNEL_TORRENT, COLLECTION_NODE)): return item["torrents"] # 'subscribed' column gets special treatment in case of ToolTipRole, because # its tooltip uses information from both 'subscribed' and 'state' keys if role == Qt.ToolTipRole and column_type == Column.SUBSCRIBED and 'subscribed' in item and 'state' in item: state_message = f" ({item['state']})" if item[ 'state'] != CHANNEL_STATE.COMPLETE.value else "" tooltip_txt = (tr("Subscribed.%s\n(Click to unsubscribe)") % state_message if item['subscribed'] else tr("Not subscribed.\n(Click to subscribe)")) return tooltip_txt return (column.tooltip_filter if role == Qt.ToolTipRole else column.display_filter)(data)
def on_channel_unsubscribe(self, channel_info): def _on_unsubscribe_action(action): if action == 0: patch_data = [{ "public_key": channel_info['public_key'], "id": channel_info['id'], "subscribed": False }] TriblerNetworkRequest( "metadata", lambda data: self.core_manager.events_manager. node_info_updated.emit(data[0]), raw_data=json.dumps(patch_data), method='PATCH', ) if self.dialog: self.dialog.close_dialog() self.dialog = None self.dialog = ConfirmationDialog( self, tr("Unsubscribe from channel"), tr("Are you sure you want to <b>unsubscribe</b> from channel<br/>") + '\"' + f"<b>{channel_info['name']}</b>" + '\"' + tr("<br/>and remove its contents?"), [(tr("UNSUBSCRIBE"), BUTTON_TYPE_NORMAL), (tr("CANCEL"), BUTTON_TYPE_CONFIRM)], ) connect(self.dialog.button_clicked, _on_unsubscribe_action) self.dialog.show()
def on_send_clicked(self, checked): self.send_report_button.setEnabled(False) self.send_report_button.setText(tr("SENDING...")) endpoint = 'http://reporter.tribler.org/report' sys_info = "" sys_info_dict = defaultdict(lambda: []) for ind in range(self.env_variables_list.topLevelItemCount()): item = self.env_variables_list.topLevelItem(ind) key = item.text(0) value = item.text(1) sys_info += f"{key}\t{value}\n" sys_info_dict[key].append(value) comments = self.comments_text_edit.toPlainText() if len(comments) == 0: comments = tr("Not provided") stack = self.error_text_edit.toPlainText() post_data = { "version": self.tribler_version, "machine": platform.machine(), "os": platform.platform(), "timestamp": int(time.time()), "sysinfo": sys_info, "comments": comments, "stack": stack, } SentryReporter.send_event(self.sentry_event, post_data, sys_info_dict, self.additional_tags) TriblerNetworkRequest(endpoint, self.on_report_sent, raw_data=tribler_urlencode(post_data), method='POST')
def perform_files_request(self): if self.closed or self.has_metainfo: return direct = not self.dialog_widget.anon_download_checkbox.isChecked() params = {'uri': self.download_uri} if direct: params['hops'] = 0 self.rest_request = TriblerNetworkRequest('torrentinfo', self.on_received_metainfo, capture_core_errors=False, url_params=params) if self.metainfo_retries <= METAINFO_MAX_RETRIES: fetch_mode = tr("directly") if direct else tr("anonymously") loading_message = tr("Loading torrent files %s...") % fetch_mode timeout_message = tr( "Timeout in fetching files %s. Retrying %i/%i") % ( fetch_mode, self.metainfo_retries, METAINFO_MAX_RETRIES, ) self.dialog_widget.loading_files_label.setText( loading_message if not self.metainfo_retries else timeout_message) self.metainfo_fetch_timer = QTimer() connect(self.metainfo_fetch_timer.timeout, self.perform_files_request) self.metainfo_fetch_timer.setSingleShot(True) self.metainfo_fetch_timer.start(METAINFO_TIMEOUT) self.metainfo_retries += 1
def on_settings_saved(self, data): if not data: return # Now save the GUI settings self.window().gui_settings.setValue( "family_filter", self.window().family_filter_checkbox.isChecked()) self.window().gui_settings.setValue( "disable_tags", self.window().disable_tags_checkbox.isChecked()) self.window().gui_settings.setValue( "autocommit_enabled", self.window().channel_autocommit_checkbox.isChecked()) self.window().gui_settings.setValue( "ask_download_settings", self.window().always_ask_location_checkbox.isChecked()) self.window().gui_settings.setValue( "use_monochrome_icon", self.window().use_monochrome_icon_checkbox.isChecked()) self.window().gui_settings.setValue( "minimize_to_tray", self.window().minimize_to_tray_checkbox.isChecked()) self.save_language_selection() self.window().tray_show_message(tr("Tribler settings"), tr("Settings saved")) self.window().fetch_settings() self.settings_edited.emit()
def on_confirm_remove_version_dirs(self, selected_versions): message_box = QMessageBox() message_box.setIcon(QMessageBox.Question) if selected_versions: version_dirs_str = "\n- ".join(selected_versions) versions_info = tr("Versions: \n- %s") % version_dirs_str title = tr("Confirm delete older versions?") message_body = tr("Are you sure to remove the selected versions? " "\nYou can not undo this action.") + ( "\n\n %s" % versions_info) message_buttons = QMessageBox.No | QMessageBox.Yes else: title = tr("No versions selected") message_body = tr("Select a version to delete.") message_buttons = QMessageBox.Close message_box.setWindowTitle(title) message_box.setText(message_body) message_box.setStandardButtons(message_buttons) user_choice = message_box.exec_() return user_choice == QMessageBox.Yes
def on_received_metainfo(self, response): if not response or not self or self.closed or self.has_metainfo: return if 'error' in response: if response['error'] == 'metainfo error': # If it failed to load metainfo for max number of times, show an error message in red. if self.metainfo_retries > METAINFO_MAX_RETRIES: self.dialog_widget.loading_files_label.setStyleSheet( "color:#ff0000;") self.dialog_widget.loading_files_label.setText( tr("Failed to load files. Click to retry again.")) return self.perform_files_request() elif 'code' in response['error'] and response['error'][ 'code'] == 'IOError': self.dialog_widget.loading_files_label.setText( tr("Unable to read torrent file data")) else: self.dialog_widget.loading_files_label.setText( tr("Error: %s") % response['error']) return metainfo = json.loads(unhexlify(response['metainfo'])) if 'files' in metainfo['info']: # Multi-file torrent files = [{ 'path': [metainfo['info']['name'], *file['path']], 'length': file['length'] } for file in metainfo['info']['files']] else: files = [{ 'path': PurePosixPath(metainfo['info']['name']).parts, 'length': metainfo['info']['length'] }] self.dialog_widget.files_list_view.fill_entries(files) # Add a bit of space between the rows self.dialog_widget.files_list_view.setStyleSheet( TORRENT_FILES_TREE_STYLESHEET + """ TorrentFileTreeWidget { background-color: #444;} TorrentFileTreeWidget::item { color: white; padding-bottom: 2px; padding-top: 2px;} """) # Show if the torrent already exists in the downloads if response.get('download_exists'): self.dialog_widget.loading_files_label.setStyleSheet( "color:#e67300;") self.dialog_widget.loading_files_label.setText( tr("Note: this torrent already exists in the Downloads")) self.has_metainfo = True self.dialog_widget.files_list_view.setHidden(False) self.dialog_widget.adjustSize() self.on_main_window_resize() self.received_metainfo.emit(metainfo)
def on_add_button_pressed(channel_id): TriblerNetworkRequest( f"collections/mychannel/{channel_id}/torrents", lambda _: self.tray_show_message( tr("Channels update"), tr("%s added to your channel") % self.chosen_dir), method='PUT', data={"torrents_dir": self.chosen_dir}, )
def on_confirm_clicked(channel_id): TriblerNetworkRequest( f"collections/mychannel/{channel_id}/copy", lambda _: self.table_view.window().tray_show_message( tr("Channel update"), tr("Torrent(s) added to your channel")), raw_data=json.dumps(entries), method='POST', )
def on_add_torrent_browse_file(self, index): self.raise_window( ) # For the case when the action is triggered by tray icon filenames = QFileDialog.getOpenFileNames( self, tr("Please select the .torrent file"), QDir.homePath(), tr("Torrent files%s") % " (*.torrent)") if len(filenames[0]) > 0: for filename in filenames[0]: self.pending_uri_requests.append(f"file:{quote(filename)}") self.process_uri_request()
def on_torrent_created(self, result): if not result: return self.dialog_widget.btn_create.setEnabled(True) self.dialog_widget.edit_channel_create_torrent_progress_label.setText(tr("Created torrent")) if 'torrent' in result: self.create_torrent_notification.emit({"msg": tr("Torrent successfully created")}) if self.dialog_widget.add_to_channel_checkbox.isChecked(): self.add_torrent_to_channel(result['torrent']) self.close_dialog()
def create_personal_menu(self): menu = TriblerActionMenu(self) delete_action = QAction(tr("Delete channel"), self) connect(delete_action.triggered, self._on_delete_action) menu.addAction(delete_action) rename_action = QAction(tr("Rename channel"), self) connect(rename_action.triggered, self._trigger_name_editor) menu.addAction(rename_action) return menu
def on_add_torrent_browse_file(self, checked): # pylint: disable=W0613 filenames = QFileDialog.getOpenFileNames( self, tr("Please select the .torrent file"), filter=(tr("Torrent files %s") % '(*.torrent)')) if not filenames[0]: return for filename in filenames[0]: self.add_torrent_to_channel(filename)
def update_torrent_size_label(self): total_files_size = self.dialog_widget.files_list_view.total_files_size selected_files_size = self.dialog_widget.files_list_view.selected_files_size if total_files_size == selected_files_size: label_text = tr("Torrent size: ") + format_size(total_files_size) else: label_text = (tr("Selected: ") + format_size(selected_files_size) + " / " + tr("Total: ") + format_size(total_files_size)) self.dialog_widget.loading_files_label.setStyleSheet("color:#ffffff;") self.dialog_widget.loading_files_label.setText(label_text)
def on_add_button_pressed(channel_id): for selected_item in self.selected_items: infohash = selected_item.download_info["infohash"] name = selected_item.download_info["name"] TriblerNetworkRequest( f"channels/mychannel/{channel_id}/torrents", lambda _: self.window().tray_show_message( tr("Channel update"), tr("Torrent(s) added to your channel")), method='PUT', data={"uri": compose_magnetlink(infohash, name=name)}, )
def on_remove_download_clicked(self, checked): self.dialog = ConfirmationDialog( self, tr("Remove download"), tr("Are you sure you want to remove this download?"), [ (tr("remove download"), BUTTON_TYPE_NORMAL), (tr("remove download + data"), BUTTON_TYPE_NORMAL), (tr("cancel"), BUTTON_TYPE_CONFIRM), ], ) connect(self.dialog.button_clicked, self.on_remove_download_dialog) self.dialog.show()
def on_report_sent(self): if self.send_automatically: self.close() success_text = tr("Successfully sent the report! Thanks for your contribution.") box = QMessageBox(self.window()) box.setWindowTitle(tr("Report Sent")) box.setText(success_text) box.setStyleSheet("QPushButton { color: white; }") box.exec_() self.close()
def on_right_click_file_item(self, pos): num_selected = len(self.window().download_files_list.selectedItems()) if num_selected == 0: return item_infos = [] # Array of (item, included, is_selected) self.selected_files_info = [] for i in range(self.window().download_files_list.topLevelItemCount()): item = self.window().download_files_list.topLevelItem(i) is_selected = item in self.window( ).download_files_list.selectedItems() item_infos.append((item, item.file_info["included"], is_selected)) if is_selected: self.selected_files_info.append(item.file_info) item_clicked = self.window().download_files_list.itemAt(pos) if not item_clicked or not item_clicked in self.window( ).download_files_list.selectedItems(): return # Check whether we should enable the 'exclude' button num_excludes = 0 num_includes_selected = 0 for item_info in item_infos: if item_info[1] and item_info[0] in self.window( ).download_files_list.selectedItems(): num_includes_selected += 1 if not item_info[1]: num_excludes += 1 menu = TriblerActionMenu(self) include_action = QAction( tr("Include files") if num_selected > 1 else tr("Include file"), self) exclude_action = QAction( tr("Exclude files") if num_selected > 1 else tr("Exclude file"), self) connect(include_action.triggered, self.on_files_included) include_action.setEnabled(True) connect(exclude_action.triggered, self.on_files_excluded) exclude_action.setEnabled(not ( num_excludes + num_includes_selected == len(item_infos))) menu.addAction(include_action) menu.addAction(exclude_action) menu.exec_(self.window().download_files_list.mapToGlobal(pos))
def start_download_from_uri(self, uri): uri = uri.decode('utf-8') if isinstance(uri, bytes) else uri self.download_uri = uri if get_gui_setting(self.gui_settings, "ask_download_settings", True, is_bool=True): # FIXME: instead of using this workaround, make sure the settings are _available_ by this moment # If tribler settings is not available, fetch the settings and inform the user to try again. if not self.tribler_settings: self.fetch_settings() self.dialog = ConfirmationDialog.show_error( self, tr("Download Error"), tr("Tribler settings is not available yet. Fetching it now. Please try again later." ), ) # By re-adding the download uri to the pending list, the request is re-processed # when the settings is received self.pending_uri_requests.append(uri) return # Clear any previous dialog if exists if self.dialog: self.dialog.close_dialog() self.dialog = None self.dialog = StartDownloadDialog(self, self.download_uri) connect(self.dialog.button_clicked, self.on_start_download_action) self.dialog.show() self.start_download_dialog_active = True else: # FIXME: instead of using this workaround, make sure the settings are _available_ by this moment # In the unlikely scenario that tribler settings are not available yet, try to fetch settings again and # add the download uri back to self.pending_uri_requests to process again. if not self.tribler_settings: self.fetch_settings() if self.download_uri not in self.pending_uri_requests: self.pending_uri_requests.append(self.download_uri) return self.window().perform_start_download_request( self.download_uri, self.window().tribler_settings['download_defaults'] ['anonymity_enabled'], self.window().tribler_settings['download_defaults'] ['safeseeding_enabled'], self.tribler_settings['download_defaults']['saveas'], [], ) self.process_uri_request()
def on_add_torrent_from_url(self, checked): # pylint: disable=W0613 self.dialog = ConfirmationDialog( self, tr("Add torrent from URL/magnet link"), tr("Please enter the URL/magnet link in the field below:"), [(tr("ADD"), BUTTON_TYPE_NORMAL), (tr("CANCEL"), BUTTON_TYPE_CONFIRM)], show_input=True, ) self.dialog.dialog_widget.dialog_input.setPlaceholderText( tr("URL/magnet link")) connect(self.dialog.button_clicked, self.on_torrent_from_url_dialog_done) self.dialog.show()
def create_channel_options_menu(self): browse_files_action = QAction(tr("Add .torrent file"), self) browse_dir_action = QAction(tr("Add torrent(s) directory"), self) add_url_action = QAction(tr("Add URL/magnet links"), self) connect(browse_files_action.triggered, self.on_add_torrent_browse_file) connect(browse_dir_action.triggered, self.on_add_torrents_browse_dir) connect(add_url_action.triggered, self.on_add_torrent_from_url) channel_options_menu = TriblerActionMenu(self) channel_options_menu.addAction(browse_files_action) channel_options_menu.addAction(browse_dir_action) channel_options_menu.addAction(add_url_action) return channel_options_menu
def on_received_metainfo(self, response): if not response or not self or self.closed: return if 'error' in response: if response['error'] == 'metainfo error': # If it failed to load metainfo for max number of times, show an error message in red. if self.metainfo_retries > METAINFO_MAX_RETRIES: self.dialog_widget.loading_files_label.setStyleSheet("color:#ff0000;") self.dialog_widget.loading_files_label.setText(tr("Failed to load files. Click to retry again.")) return self.perform_files_request() elif 'code' in response['error'] and response['error']['code'] == 'IOError': self.dialog_widget.loading_files_label.setText(tr("Unable to read torrent file data")) else: self.dialog_widget.loading_files_label.setText(tr("Error: %s") % response['error']) return metainfo = json.loads(unhexlify(response['metainfo'])) if 'files' in metainfo['info']: # Multi-file torrent files = metainfo['info']['files'] else: files = [{'path': [metainfo['info']['name']], 'length': metainfo['info']['length']}] # Show if the torrent already exists in the downloads if response.get('download_exists'): self.dialog_widget.existing_download_info_label.setText( tr("Note: this torrent already exists in the Downloads") ) else: self.dialog_widget.existing_download_info_label.setText("") self.dialog_widget.files_list_view.clear() for filename in files: item = DownloadFileTreeWidgetItem(self.dialog_widget.files_list_view) item.setText(0, '/'.join(filename['path'])) item.setText(1, format_size(float(filename['length']))) item.setData(0, Qt.UserRole, filename) item.setCheckState(2, Qt.Checked) self.dialog_widget.files_list_view.addTopLevelItem(item) self.has_metainfo = True self.dialog_widget.loading_files_label.setHidden(True) self.dialog_widget.download_files_container.setHidden(False) self.dialog_widget.files_list_view.setHidden(False) self.dialog_widget.adjustSize() self.on_main_window_resize() self.received_metainfo.emit(metainfo)
def on_choose_log_dir_clicked(self, checked): previous_log_dir = self.window().log_location_input.text() or "" log_dir = QFileDialog.getExistingDirectory( self.window(), tr("Please select the log directory"), previous_log_dir, QFileDialog.ShowDirsOnly ) if not log_dir or log_dir == previous_log_dir: return is_writable, error = is_dir_writable(log_dir) if not is_writable: gui_error_message = f"<i>{log_dir}</i> is not writable. [{error}]" ConfirmationDialog.show_message(self.window(), tr("Insufficient Permissions"), gui_error_message, "OK") else: self.window().log_location_input.setText(log_dir)
def clicked_skip_conversion(self): self.dialog = ConfirmationDialog( self, tr("Abort the conversion of Channels database"), tr("The upgrade procedure is now <b>converting your personal channel</b> and channels " "collected by the previous installation of Tribler.<br>" "Are you sure you want to abort the conversion process?<br><br>" "<p style='color:red'><b> !!! WARNING !!! <br>" "You will lose your personal channel and subscribed channels if you ABORT now! </b> </p> <br>" ), [(tr("ABORT"), BUTTON_TYPE_CONFIRM), (tr("CONTINUE"), BUTTON_TYPE_NORMAL)], ) connect(self.dialog.button_clicked, self.on_skip_conversion_dialog) self.dialog.show()
def show_results(self, *_): if self.search_request is None: # Fixes a race condition where the user clicks the show_results button before the search request # has been registered by the Core return self.timeout_progress_bar.stop() query = self.search_request.query self.results_page.initialize_root_model( SearchResultsModel( channel_info={ "name": (tr("Search results for %s") % query.original_query) if len(query.original_query) < 50 else f"{query.original_query[:50]}..." }, endpoint_url="search", hide_xxx=self.results_page.hide_xxx, text_filter=to_fts_query(query.fts_text), tags=list(query.tags), type_filter=[ REGULAR_TORRENT, CHANNEL_TORRENT, COLLECTION_NODE ], )) self.setCurrentWidget(self.results_page) # After transitioning to the page with search results, we refresh the viewport since some rows might have been # rendered already with an incorrect row height. self.results_page.run_brain_dead_refresh()