def on_low_storage(self, _): """ Dealing with low storage space available. First stop the downloads and the core manager and ask user to user to make free space. :return: """ def close_tribler_gui(): self.close_tribler() # Since the core has already stopped at this point, it will not terminate the GUI. # So, we quit the GUI separately here. if not QApplication.closingDown(): QApplication.quit() self.downloads_page.stop_loading_downloads() self.core_manager.stop(False) close_dialog = ConfirmationDialog( self.window(), tr("<b>CRITICAL ERROR</b>"), tr( "You are running low on disk space (<100MB). Please make sure to have " "sufficient free space available and restart Tribler again." ), [(tr("Close Tribler"), BUTTON_TYPE_NORMAL)], ) connect(close_dialog.button_clicked, lambda _: close_tribler_gui()) close_dialog.show()
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_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_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_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( "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.saved_dialog = ConfirmationDialog( TriblerRequestManager.window, tr("Settings saved"), tr("Your settings have been saved."), [(tr("CLOSE"), BUTTON_TYPE_NORMAL)], ) connect(self.saved_dialog.button_clicked, self.on_dialog_cancel_clicked) self.saved_dialog.show() self.window().fetch_settings()
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 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_add_torrent_browse_file(self, index): 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_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_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=dumps(entries), method='POST', )
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
class PopularTorrentsModel(ChannelContentModel): columns = ['category', 'name', 'size', 'updated'] column_headers = ['', tr('Name'), tr('Size'), tr('Updated')] column_width = dict(ChannelContentModel.column_width, **{'name': lambda table_width: table_width - 280}) def __init__(self, *args, **kwargs): kwargs["endpoint_url"] = 'channels/popular_torrents' super().__init__(*args, **kwargs)
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 on_tribler_started(self, version): if self.tribler_started: logging.warning("Received duplicate Tribler Core started event") return self.tribler_started = True self.tribler_version = version self.top_menu_button.setHidden(False) self.left_menu.setHidden(False) self.token_balance_widget.setHidden(False) self.settings_button.setHidden(False) self.add_torrent_button.setHidden(False) self.top_search_bar.setHidden(False) self.fetch_settings() self.downloads_page.start_loading_downloads() self.setAcceptDrops(True) self.setWindowTitle(f"Tribler {self.tribler_version}") autocommit_enabled = ( get_gui_setting(self.gui_settings, "autocommit_enabled", True, is_bool=True) if self.gui_settings else True ) self.channel_contents_page.initialize_content_page(autocommit_enabled=autocommit_enabled, hide_xxx=False) hide_xxx = get_gui_setting(self.gui_settings, "family_filter", True, is_bool=True) self.discovered_page.initialize_root_model( DiscoveredChannelsModel( channel_info={"name": tr("Discovered channels")}, endpoint_url="channels", hide_xxx=hide_xxx ) ) connect(self.core_manager.events_manager.discovered_channel, self.discovered_page.model.on_new_entry_received) self.popular_page.initialize_root_model( PopularTorrentsModel(channel_info={"name": "Popular torrents"}, hide_xxx=hide_xxx) ) self.popular_page.explanation_text.setText( tr("This page show the list of popular torrents collected by Tribler during the last 24 hours.") ) self.popular_page.explanation_container.setHidden(False) self.add_to_channel_dialog.load_channel(0) if not self.gui_settings.value("first_discover", False) and not self.core_manager.use_existing_core: connect(self.core_manager.events_manager.discovered_channel, self.stop_discovering) self.window().gui_settings.setValue("first_discover", True) self.discovering_page.is_discovering = True self.stackedWidget.setCurrentIndex(PAGE_DISCOVERING) else: self.clicked_menu_button_discovered() self.left_menu_button_discovered.setChecked(True) self.channels_menu_list.load_channels()
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_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 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_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
class SimplifiedPersonalChannelsModel(PersonalChannelsModel): columns = [ACTION_BUTTONS, 'category', 'name', 'size', 'health', 'updated'] column_headers = [ '', '', tr('Name'), tr('Size'), tr('Health'), tr('Updated') ] column_width = dict(ChannelContentModel.column_width, **{'name': lambda table_width: table_width - 440}) def __init__(self, *args, **kwargs): kwargs["exclude_deleted"] = kwargs.get("exclude_deleted", True) super().__init__(*args, **kwargs)
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 on_add_button_pressed(channel_id): post_data = {} if uri.startswith("file:"): with open(uri[5:], "rb") as torrent_file: post_data['torrent'] = b64encode(torrent_file.read()).decode('utf8') elif uri.startswith("magnet:"): post_data['uri'] = uri if post_data: TriblerNetworkRequest( f"channels/mychannel/{channel_id}/torrents", lambda _: self.tray_show_message(tr("Channel update"), tr("Torrent(s) added to your channel")), method='PUT', data=post_data, )
def close_tribler(self, checked=False): if self.core_manager.shutting_down: return def show_force_shutdown(): self.window().force_shutdown_btn.show() self.delete_tray_icon() self.show_loading_screen() self.hide_status_bar() self.loading_text_label.setText(tr("Shutting down...")) if self.debug_window: self.debug_window.setHidden(True) self.shutdown_timer = QTimer() connect(self.shutdown_timer.timeout, show_force_shutdown) self.shutdown_timer.start(SHUTDOWN_WAITING_PERIOD) self.gui_settings.setValue("pos", self.pos()) self.gui_settings.setValue("size", self.size()) if self.core_manager.use_existing_core: # Don't close the core that we are using QApplication.quit() self.core_manager.stop() self.core_manager.shutting_down = True self.downloads_page.stop_loading_downloads() request_manager.clear() # Stop the token balance timer if self.token_refresh_timer: self.token_refresh_timer.stop()
def on_confirm_add_directory_dialog(self, action): if action == 0: if self.dialog.checkbox.isChecked(): # TODO: add recursive directory scanning 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}, ) self.window().add_to_channel_dialog.show_dialog( on_add_button_pressed, confirm_button_text=tr("Add torrent(s)") ) for torrent_file in self.selected_torrent_files: self.perform_start_download_request( f"file:{torrent_file}", self.window().tribler_settings['download_defaults']['anonymity_enabled'], self.window().tribler_settings['download_defaults']['safeseeding_enabled'], self.tribler_settings['download_defaults']['saveas'], [], ) if self.dialog: self.dialog.close_dialog() self.dialog = None
def on_top_search_button_click(self): current_ts = time.time() query = self.top_search_bar.text() if ( self.last_search_query and self.last_search_time and self.last_search_query == self.top_search_bar.text() and current_ts - self.last_search_time < 1 ): logging.info("Same search query already sent within 500ms so dropping this one") return if not query: return self.has_search_results = True self.search_results_page.initialize_root_model( SearchResultsModel( channel_info={"name": (tr("Search results for %s") % query) if len(query) < 50 else f"{query[:50]}..."}, endpoint_url="search", hide_xxx=get_gui_setting(self.gui_settings, "family_filter", True, is_bool=True), text_filter=to_fts_query(query), type_filter=[REGULAR_TORRENT, CHANNEL_TORRENT, COLLECTION_NODE], ) ) self.clicked_search_bar() # Trigger remote search self.search_results_page.preview_clicked() self.last_search_query = query self.last_search_time = current_ts
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 OSError as exc: ConfirmationDialog.show_error( self.window(), tr("Error when exporting file"), tr("An error occurred when exporting the torrent file: %s") % str(exc), ) else: self.window().tray_show_message( tr("Torrent file exported"), tr("Torrent file exported to %s") % str(dest_path))
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 on_add_torrent_from_url(self, checked=False): # Make sure that the window is visible (this action might be triggered from the tray icon) self.raise_window() if not self.add_torrent_url_dialog_active: 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")) self.dialog.dialog_widget.dialog_input.setFocus() connect(self.dialog.button_clicked, self.on_torrent_from_url_dialog_done) self.dialog.show() self.add_torrent_url_dialog_active = True
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()