class SubscribedChannelsPage(QWidget): """ This page shows all the channels that the user has subscribed to. """ def __init__(self): QWidget.__init__(self) self.dialog = None self.request_mgr = None def initialize(self): self.window().add_subscription_button.clicked.connect(self.on_add_subscription_clicked) def load_subscribed_channels(self): self.window().subscribed_channels_list.set_data_items([(LoadingListItem, None)]) self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("channels/subscribed", self.received_subscribed_channels) def received_subscribed_channels(self, results): if not results: return self.window().subscribed_channels_list.set_data_items([]) items = [] if len(results['subscribed']) == 0: self.window().subscribed_channels_list.set_data_items( [(LoadingListItem, "You are not subscribed to any channel.")]) return for result in results['subscribed']: items.append((ChannelListItem, result)) self.window().subscribed_channels_list.set_data_items(items) def on_add_subscription_clicked(self): self.dialog = ConfirmationDialog(self, "Add subscribed channel", "Please enter the identifier of the channel you want to subscribe to below. " "It can take up to a minute before the channel is visible in your list of " "subscribed channels.", [('ADD', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)], show_input=True) self.dialog.dialog_widget.dialog_input.setPlaceholderText('Channel identifier') self.dialog.button_clicked.connect(self.on_subscription_added) self.dialog.show() def on_subscription_added(self, action): if action == 0: self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("channels/subscribed/%s" % self.dialog.dialog_widget.dialog_input.text(), self.on_channel_subscribed, method='PUT') self.dialog.close_dialog() self.dialog = None def on_channel_subscribed(self, _): pass
def __init__(self, title, main_text): super(CustomConfDialog, self).__init__() self.setObjectName("Tribler") self.setWindowTitle("Tribler: Critical Error!") self.resize(500, 300) dlg = ConfirmationDialog(self, title, main_text, [('CLOSE', BUTTON_TYPE_NORMAL)]) dlg.show() dlg.button_clicked.connect(lambda: sys.exit(1))
def show_error(self, error_text): main_text = "An error occurred during the request:\n\n%s" % error_text error_dialog = ConfirmationDialog(TriblerRequestManager.window, "Request error", main_text, [('CLOSE', BUTTON_TYPE_NORMAL)]) def on_close(): error_dialog.setParent(None) error_dialog.button_clicked.connect(on_close) error_dialog.show()
def show_error(self, error_text): main_text = "An error occurred during the request:\n\n%s" % error_text error_dialog = ConfirmationDialog(TriblerRequestManager.window, "Request error", main_text, [('CLOSE', BUTTON_TYPE_NORMAL)]) def on_close(): error_dialog.close_dialog() error_dialog.button_clicked.connect(on_close) error_dialog.show()
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: """ self.downloads_page.stop_loading_downloads() self.core_manager.stop(False) close_dialog = ConfirmationDialog(self.window(), "<b>CRITICAL ERROR</b>", "You are running low on disk space (<100MB). Please make sure to have " "sufficient free space available and restart Tribler again.", [("Close Tribler", BUTTON_TYPE_NORMAL)]) close_dialog.button_clicked.connect(lambda _: self.close_tribler()) close_dialog.show()
def on_export_mdblob(self): export_dir = QFileDialog.getExistingDirectory( self, "Please select the destination directory", "", QFileDialog.ShowDirsOnly) if len(export_dir) == 0: return # Show confirmation dialog where we specify the name of the file mdblob_name = self.channel_overview["public_key"] dialog = ConfirmationDialog( self, "Export mdblob file", "Please enter the name of the channel metadata file:", [('SAVE', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)], show_input=True) 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_request_done(dest_path, data): 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) dialog.dialog_widget.dialog_input.setPlaceholderText( 'Channel file name') dialog.dialog_widget.dialog_input.setText("%s.mdblob" % mdblob_name) dialog.dialog_widget.dialog_input.setFocus() dialog.button_clicked.connect(on_export_download_dialog_done) dialog.show()
def on_export_mdblob(self): export_dir = QFileDialog.getExistingDirectory(self, "Please select the destination directory", "", QFileDialog.ShowDirsOnly) if len(export_dir) == 0: return # Show confirmation dialog where we specify the name of the file mdblob_name = self.channel_overview["identifier"] dialog = ConfirmationDialog(self, "Export mdblob file", "Please enter the name of the channel metadata file:", [('SAVE', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)], show_input=True) 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_request_done(dest_path, data): 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) dialog.dialog_widget.dialog_input.setPlaceholderText('Channel file name') dialog.dialog_widget.dialog_input.setText("%s.mdblob" % mdblob_name) dialog.dialog_widget.dialog_input.setFocus() dialog.button_clicked.connect(on_export_download_dialog_done) dialog.show()
class SettingsPage(QWidget): """ This class is responsible for displaying and adjusting the settings present in Tribler. """ def __init__(self): QWidget.__init__(self) self.settings = None self.settings_request_mgr = None self.trustchain_request_mgr = None self.saved_dialog = None self.empty_tokens_barcode_dialog = None self.empty_partial_tokens_dialog = None self.confirm_empty_tokens_dialog = None def initialize_settings_page(self): self.window().settings_tab.initialize() self.window().settings_tab.clicked_tab_button.connect( self.clicked_tab_button) self.window().settings_save_button.clicked.connect(self.save_settings) self.window().download_location_chooser_button.clicked.connect( self.on_choose_download_dir_clicked) self.window().watch_folder_chooser_button.clicked.connect( self.on_choose_watch_dir_clicked) self.window().developer_mode_enabled_checkbox.stateChanged.connect( self.on_developer_mode_checkbox_changed) self.window().use_monochrome_icon_checkbox.stateChanged.connect( self.on_use_monochrome_icon_checkbox_changed) self.window().download_settings_anon_checkbox.stateChanged.connect( self.on_anon_download_state_changed) self.window().fully_empty_tokens_button.clicked.connect( self.confirm_fully_empty_tokens) self.window().partially_empty_tokens_button.clicked.connect( self.partially_empty_tokens) self.window().log_location_chooser_button.clicked.connect( self.on_choose_log_dir_clicked) self.update_stacked_widget_height() def confirm_fully_empty_tokens(self): self.confirm_empty_tokens_dialog = ConfirmationDialog( self, "Empty tokens into another account", "Are you sure you want to empty ALL bandwidth tokens " "into another account? " "Warning: one-way action that cannot be revered", [('EMPTY', BUTTON_TYPE_CONFIRM), ('CANCEL', BUTTON_TYPE_NORMAL)]) self.confirm_empty_tokens_dialog.button_clicked.connect( self.on_confirm_fully_empty_tokens) self.confirm_empty_tokens_dialog.show() def on_confirm_fully_empty_tokens(self, action): self.confirm_empty_tokens_dialog.close_dialog() self.confirm_empty_tokens_dialog = None if action == 0: self.trustchain_request_mgr = TriblerRequestManager() self.trustchain_request_mgr.perform_request( "trustchain/bootstrap", self.on_emptying_tokens) def partially_empty_tokens(self): self.empty_partial_tokens_dialog = ConfirmationDialog( self, "Empty tokens into another account", "Specify the amount of bandwidth tokens to empty into " "another account below:", [('EMPTY', BUTTON_TYPE_CONFIRM), ('CANCEL', BUTTON_TYPE_NORMAL)], show_input=True) self.empty_partial_tokens_dialog.dialog_widget.dialog_input.setPlaceholderText( 'Please enter the amount of tokens in MB') self.empty_partial_tokens_dialog.dialog_widget.dialog_input.setFocus() self.empty_partial_tokens_dialog.button_clicked.connect( self.confirm_partially_empty_tokens) self.empty_partial_tokens_dialog.show() def confirm_partially_empty_tokens(self, action): tokens = self.empty_partial_tokens_dialog.dialog_widget.dialog_input.text( ) self.empty_partial_tokens_dialog.close_dialog() self.empty_partial_tokens_dialog = None if action == 0: try: tokens = int(float(tokens)) except ValueError: ConfirmationDialog.show_error( self.window(), "Wrong input", "The provided amount is not a number") return self.confirm_empty_tokens_dialog = ConfirmationDialog( self, "Empty tokens into another account", "Are you sure you want to empty %d bandwidth tokens " "into another account? " "Warning: one-way action that cannot be revered" % tokens, [('EMPTY', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)]) self.confirm_empty_tokens_dialog.button_clicked.connect( lambda action2: self.on_confirm_partially_empty_tokens( action2, tokens)) self.confirm_empty_tokens_dialog.show() def on_confirm_partially_empty_tokens(self, action, tokens): self.confirm_empty_tokens_dialog.close_dialog() self.confirm_empty_tokens_dialog = None if action == 0: self.trustchain_request_mgr = TriblerRequestManager() self.trustchain_request_mgr.perform_request( "trustchain/bootstrap?amount=%d" % (tokens * MEBIBYTE), self.on_emptying_tokens) def on_emptying_tokens(self, data): json_data = json.dumps(data) if has_qr: self.empty_tokens_barcode_dialog = QWidget() self.empty_tokens_barcode_dialog.setWindowTitle( "Please scan the following QR code") self.empty_tokens_barcode_dialog.setGeometry(10, 10, 500, 500) qr = qrcode.QRCode( version=1, error_correction=qrcode.constants.ERROR_CORRECT_M, box_size=10, border=5, ) qr.add_data(json_data) qr.make(fit=True) img = qr.make_image() # PIL format qim = ImageQt(img) pixmap = QtGui.QPixmap.fromImage(qim).scaled( 600, 600, QtCore.Qt.KeepAspectRatio) label = QLabel(self.empty_tokens_barcode_dialog) label.setPixmap(pixmap) self.empty_tokens_barcode_dialog.resize(pixmap.width(), pixmap.height()) self.empty_tokens_barcode_dialog.show() else: ConfirmationDialog.show_error(self.window(), DEPENDENCY_ERROR_TITLE, DEPENDENCY_ERROR_MESSAGE) def on_developer_mode_checkbox_changed(self, _): self.window().gui_settings.setValue( "debug", self.window().developer_mode_enabled_checkbox.isChecked()) self.window().left_menu_button_debug.setHidden( not self.window().developer_mode_enabled_checkbox.isChecked()) def on_use_monochrome_icon_checkbox_changed(self, _): use_monochrome_icon = self.window( ).use_monochrome_icon_checkbox.isChecked() self.window().gui_settings.setValue("use_monochrome_icon", use_monochrome_icon) self.window().update_tray_icon(use_monochrome_icon) def on_anon_download_state_changed(self, _): if self.window().download_settings_anon_checkbox.isChecked(): self.window().download_settings_anon_seeding_checkbox.setChecked( True) self.window().download_settings_anon_seeding_checkbox.setEnabled( not self.window().download_settings_anon_checkbox.isChecked()) def on_choose_download_dir_clicked(self): previous_download_path = self.window().download_location_input.text( ) or "" download_dir = QFileDialog.getExistingDirectory( self.window(), "Please select the download location", previous_download_path, QFileDialog.ShowDirsOnly) if not download_dir: return self.window().download_location_input.setText(download_dir) def on_choose_watch_dir_clicked(self): if self.window().watchfolder_enabled_checkbox.isChecked(): previous_watch_dir = self.window().watchfolder_location_input.text( ) or "" watch_dir = QFileDialog.getExistingDirectory( self.window(), "Please select the watch folder", previous_watch_dir, QFileDialog.ShowDirsOnly) if not watch_dir: return self.window().watchfolder_location_input.setText(watch_dir) def on_choose_log_dir_clicked(self): previous_log_dir = self.window().log_location_input.text() or "" log_dir = QFileDialog.getExistingDirectory( self.window(), "Please select the log directory", previous_log_dir, QFileDialog.ShowDirsOnly) if not log_dir or log_dir == previous_log_dir: return if not is_dir_writable(log_dir): ConfirmationDialog.show_message( self.window(), "Insufficient Permissions", "<i>%s</i> is not writable. " % log_dir, "OK") else: self.window().log_location_input.setText(log_dir) def initialize_with_settings(self, settings): self.settings = settings settings = settings["settings"] gui_settings = self.window().gui_settings # General settings self.window().family_filter_checkbox.setChecked( settings['general']['family_filter']) self.window().use_monochrome_icon_checkbox.setChecked( get_gui_setting(gui_settings, "use_monochrome_icon", False, is_bool=True)) self.window().download_location_input.setText( settings['download_defaults']['saveas']) self.window().always_ask_location_checkbox.setChecked( get_gui_setting(gui_settings, "ask_download_settings", True, is_bool=True)) self.window().download_settings_anon_checkbox.setChecked( settings['download_defaults']['anonymity_enabled']) self.window().download_settings_anon_seeding_checkbox.setChecked( settings['download_defaults']['safeseeding_enabled']) self.window().watchfolder_enabled_checkbox.setChecked( settings['watch_folder']['enabled']) self.window().watchfolder_location_input.setText( settings['watch_folder']['directory']) # Log directory self.window().log_location_input.setText( settings['general']['log_dir']) # Connection settings self.window().lt_proxy_type_combobox.setCurrentIndex( settings['libtorrent']['proxy_type']) if settings['libtorrent']['proxy_server']: self.window().lt_proxy_server_input.setText( settings['libtorrent']['proxy_server'][0]) self.window().lt_proxy_port_input.setText( "%s" % settings['libtorrent']['proxy_server'][1]) if settings['libtorrent']['proxy_auth']: self.window().lt_proxy_username_input.setText( settings['libtorrent']['proxy_auth'][0]) self.window().lt_proxy_password_input.setText( settings['libtorrent']['proxy_auth'][1]) self.window().lt_utp_checkbox.setChecked(settings['libtorrent']['utp']) max_conn_download = settings['libtorrent']['max_connections_download'] if max_conn_download == -1: max_conn_download = 0 self.window().max_connections_download_input.setText( str(max_conn_download)) self.window().api_port_input.setText( "%s" % get_gui_setting(gui_settings, "api_port", DEFAULT_API_PORT)) # Bandwidth settings self.window().upload_rate_limit_input.setText( str(settings['libtorrent']['max_upload_rate'] / 1024)) self.window().download_rate_limit_input.setText( str(settings['libtorrent']['max_download_rate'] / 1024)) # Seeding settings getattr( self.window(), "seeding_" + settings['download_defaults']['seeding_mode'] + "_radio").setChecked(True) self.window().seeding_time_input.setText( seconds_to_hhmm_string( settings['download_defaults']['seeding_time'])) ind = self.window().seeding_ratio_combobox.findText( str(settings['download_defaults']['seeding_ratio'])) if ind != -1: self.window().seeding_ratio_combobox.setCurrentIndex(ind) # Anonymity settings self.window().allow_exit_node_checkbox.setChecked( settings['tunnel_community']['exitnode_enabled']) self.window().number_hops_slider.setValue( int(settings['download_defaults']['number_hops']) - 1) self.window().credit_mining_enabled_checkbox.setChecked( settings['credit_mining']['enabled']) self.window().max_disk_space_input.setText( str(settings['credit_mining']['max_disk_space'])) # Debug self.window().developer_mode_enabled_checkbox.setChecked( get_gui_setting(gui_settings, "debug", False, is_bool=True)) self.window().checkbox_enable_resource_log.setChecked( settings['resource_monitor']['enabled']) cpu_priority = 1 if 'cpu_priority' in settings['resource_monitor']: cpu_priority = int(settings['resource_monitor']['cpu_priority']) self.window().slider_cpu_level.setValue(cpu_priority) self.window().cpu_priority_value.setText("Current Priority = %s" % cpu_priority) self.window().slider_cpu_level.valueChanged.connect( self.show_updated_cpu_priority) def show_updated_cpu_priority(self, value): self.window().cpu_priority_value.setText("Current Priority = %s" % value) def load_settings(self): self.settings_request_mgr = TriblerRequestManager() self.settings_request_mgr.perform_request( "settings", self.initialize_with_settings) def clicked_tab_button(self, tab_button_name): if tab_button_name == "settings_general_button": self.window().settings_stacked_widget.setCurrentIndex( PAGE_SETTINGS_GENERAL) elif tab_button_name == "settings_connection_button": self.window().settings_stacked_widget.setCurrentIndex( PAGE_SETTINGS_CONNECTION) elif tab_button_name == "settings_bandwidth_button": self.window().settings_stacked_widget.setCurrentIndex( PAGE_SETTINGS_BANDWIDTH) elif tab_button_name == "settings_seeding_button": self.window().settings_stacked_widget.setCurrentIndex( PAGE_SETTINGS_SEEDING) elif tab_button_name == "settings_anonymity_button": self.window().settings_stacked_widget.setCurrentIndex( PAGE_SETTINGS_ANONYMITY) elif tab_button_name == "settings_debug_button": self.window().settings_stacked_widget.setCurrentIndex( PAGE_SETTINGS_DEBUG) self.update_stacked_widget_height() def update_stacked_widget_height(self): """ Update the height of the settings tab. This is required since the height of a QStackedWidget is by default the height of the largest page. This messes up the scroll bar. """ for index in range(self.window().settings_stacked_widget.count()): if index == self.window().settings_stacked_widget.currentIndex(): self.window().settings_stacked_widget.setSizePolicy( QSizePolicy.Preferred, QSizePolicy.Preferred) else: self.window().settings_stacked_widget.setSizePolicy( QSizePolicy.Ignored, QSizePolicy.Ignored) self.window().settings_stacked_widget.adjustSize() def save_settings(self): # Create a dictionary with all available settings settings_data = { 'general': {}, 'Tribler': {}, 'download_defaults': {}, 'libtorrent': {}, 'watch_folder': {}, 'tunnel_community': {}, 'trustchain': {}, 'credit_mining': {}, 'resource_monitor': {} } settings_data['general']['family_filter'] = self.window( ).family_filter_checkbox.isChecked() settings_data['download_defaults']['saveas'] = self.window( ).download_location_input.text().encode('utf-8') settings_data['general']['log_dir'] = self.window( ).log_location_input.text() settings_data['watch_folder']['enabled'] = self.window( ).watchfolder_enabled_checkbox.isChecked() if settings_data['watch_folder']['enabled']: settings_data['watch_folder']['directory'] = self.window( ).watchfolder_location_input.text() settings_data['libtorrent']['proxy_type'] = self.window( ).lt_proxy_type_combobox.currentIndex() if self.window().lt_proxy_server_input.text() and len( self.window().lt_proxy_server_input.text()) > 0 and len( self.window().lt_proxy_port_input.text()) > 0: settings_data['libtorrent']['proxy_server'] = [ self.window().lt_proxy_server_input.text(), None ] settings_data['libtorrent']['proxy_server'][0] = self.window( ).lt_proxy_server_input.text() try: settings_data['libtorrent']['proxy_server'][1] = int( self.window().lt_proxy_port_input.text()) except ValueError: ConfirmationDialog.show_error( self.window(), "Invalid proxy port number", "You've entered an invalid format for the proxy port number. " "Please enter a whole number.") return if len(self.window().lt_proxy_username_input.text()) > 0 and \ len(self.window().lt_proxy_password_input.text()) > 0: settings_data['libtorrent']['proxy_auth'] = [None, None] settings_data['libtorrent']['proxy_auth'][0] = self.window( ).lt_proxy_username_input.text() settings_data['libtorrent']['proxy_auth'][1] = self.window( ).lt_proxy_password_input.text() settings_data['libtorrent']['utp'] = self.window( ).lt_utp_checkbox.isChecked() try: max_conn_download = int( self.window().max_connections_download_input.text()) except ValueError: ConfirmationDialog.show_error( self.window(), "Invalid number of connections", "You've entered an invalid format for the maximum number of connections. " "Please enter a whole number.") return if max_conn_download == 0: max_conn_download = -1 settings_data['libtorrent'][ 'max_connections_download'] = max_conn_download try: if self.window().upload_rate_limit_input.text(): user_upload_rate_limit = int( self.window().upload_rate_limit_input.text()) * 1024 if user_upload_rate_limit < sys.maxsize: settings_data['libtorrent'][ 'max_upload_rate'] = user_upload_rate_limit else: raise ValueError if self.window().download_rate_limit_input.text(): user_download_rate_limit = int( self.window().download_rate_limit_input.text()) * 1024 if user_download_rate_limit < sys.maxsize: settings_data['libtorrent'][ 'max_download_rate'] = user_download_rate_limit else: raise ValueError except ValueError: ConfirmationDialog.show_error( self.window(), "Invalid value for bandwidth limit", "You've entered an invalid value for the maximum upload/download rate. " "Please enter a whole number (max: %d)" % (sys.maxsize / 1000)) return try: if self.window().api_port_input.text(): api_port = int(self.window().api_port_input.text()) if api_port <= 0 or api_port >= 65536: raise ValueError() self.window().gui_settings.setValue("api_port", api_port) except ValueError: ConfirmationDialog.show_error( self.window(), "Invalid value for api port", "Please enter a valid port for the api (between 0 and 65536)") return seeding_modes = ['forever', 'time', 'never', 'ratio'] selected_mode = 'forever' for seeding_mode in seeding_modes: if getattr(self.window(), "seeding_" + seeding_mode + "_radio").isChecked(): selected_mode = seeding_mode break settings_data['download_defaults']['seeding_mode'] = selected_mode settings_data['download_defaults']['seeding_ratio'] = self.window( ).seeding_ratio_combobox.currentText() try: settings_data['download_defaults'][ 'seeding_time'] = string_to_seconds( self.window().seeding_time_input.text()) except ValueError: ConfirmationDialog.show_error( self.window(), "Invalid seeding time", "You've entered an invalid format for the seeding time (expected HH:MM)" ) return settings_data['credit_mining']['enabled'] = self.window( ).credit_mining_enabled_checkbox.isChecked() settings_data['credit_mining']['max_disk_space'] = int( self.window().max_disk_space_input.text()) settings_data['tunnel_community']['exitnode_enabled'] = self.window( ).allow_exit_node_checkbox.isChecked() settings_data['download_defaults']['number_hops'] = self.window( ).number_hops_slider.value() + 1 settings_data['download_defaults']['anonymity_enabled'] = \ self.window().download_settings_anon_checkbox.isChecked() settings_data['download_defaults']['safeseeding_enabled'] = \ self.window().download_settings_anon_seeding_checkbox.isChecked() settings_data['resource_monitor']['enabled'] = self.window( ).checkbox_enable_resource_log.isChecked() settings_data['resource_monitor']['cpu_priority'] = int( self.window().slider_cpu_level.value()) self.window().settings_save_button.setEnabled(False) self.settings_request_mgr = TriblerRequestManager() self.settings_request_mgr.perform_request( "settings", self.on_settings_saved, method='POST', data=json.dumps(settings_data)) def on_settings_saved(self, _): # Now save the GUI settings 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, "Settings saved", "Your settings have been saved.", [('CLOSE', BUTTON_TYPE_NORMAL)]) self.saved_dialog.button_clicked.connect(self.on_dialog_cancel_clicked) self.saved_dialog.show() self.window().fetch_settings() def on_dialog_cancel_clicked(self, _): self.window().settings_save_button.setEnabled(True) self.saved_dialog.close_dialog() self.saved_dialog = None
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 SettingsPage(QWidget): """ This class is responsible for displaying and adjusting the settings present in Tribler. """ def __init__(self): QWidget.__init__(self) self.settings = None self.settings_request_mgr = None self.trustchain_request_mgr = None self.saved_dialog = None self.empty_tokens_barcode_dialog = None self.empty_partial_tokens_dialog = None self.confirm_empty_tokens_dialog = None def initialize_settings_page(self): self.window().settings_tab.initialize() self.window().settings_tab.clicked_tab_button.connect(self.clicked_tab_button) self.window().settings_save_button.clicked.connect(self.save_settings) self.window().download_location_chooser_button.clicked.connect(self.on_choose_download_dir_clicked) self.window().watch_folder_chooser_button.clicked.connect(self.on_choose_watch_dir_clicked) self.window().channel_autocommit_checkbox.stateChanged.connect(self.on_channel_autocommit_checkbox_changed) self.window().family_filter_checkbox.stateChanged.connect(self.on_family_filter_checkbox_changed) self.window().developer_mode_enabled_checkbox.stateChanged.connect(self.on_developer_mode_checkbox_changed) self.window().use_monochrome_icon_checkbox.stateChanged.connect(self.on_use_monochrome_icon_checkbox_changed) self.window().download_settings_anon_checkbox.stateChanged.connect(self.on_anon_download_state_changed) self.window().fully_empty_tokens_button.clicked.connect(self.confirm_fully_empty_tokens) self.window().partially_empty_tokens_button.clicked.connect(self.partially_empty_tokens) self.window().log_location_chooser_button.clicked.connect(self.on_choose_log_dir_clicked) self.update_stacked_widget_height() def confirm_fully_empty_tokens(self): self.confirm_empty_tokens_dialog = ConfirmationDialog(self, "Empty tokens into another account", "Are you sure you want to empty ALL bandwidth tokens " "into another account? " "Warning: one-way action that cannot be revered", [ ('EMPTY', BUTTON_TYPE_CONFIRM), ('CANCEL', BUTTON_TYPE_NORMAL) ]) self.confirm_empty_tokens_dialog.button_clicked.connect(self.on_confirm_fully_empty_tokens) self.confirm_empty_tokens_dialog.show() def on_confirm_fully_empty_tokens(self, action): self.confirm_empty_tokens_dialog.close_dialog() self.confirm_empty_tokens_dialog = None if action == 0: self.trustchain_request_mgr = TriblerRequestManager() self.trustchain_request_mgr.perform_request("trustchain/bootstrap", self.on_emptying_tokens) def partially_empty_tokens(self): self.empty_partial_tokens_dialog = ConfirmationDialog(self, "Empty tokens into another account", "Specify the amount of bandwidth tokens to empty into " "another account below:", [ ('EMPTY', BUTTON_TYPE_CONFIRM), ('CANCEL', BUTTON_TYPE_NORMAL) ], show_input=True) self.empty_partial_tokens_dialog.dialog_widget.dialog_input.setPlaceholderText( 'Please enter the amount of tokens in MB') self.empty_partial_tokens_dialog.dialog_widget.dialog_input.setFocus() self.empty_partial_tokens_dialog.button_clicked.connect(self.confirm_partially_empty_tokens) self.empty_partial_tokens_dialog.show() def confirm_partially_empty_tokens(self, action): tokens = self.empty_partial_tokens_dialog.dialog_widget.dialog_input.text() self.empty_partial_tokens_dialog.close_dialog() self.empty_partial_tokens_dialog = None if action == 0: try: tokens = int(float(tokens)) except ValueError: ConfirmationDialog.show_error(self.window(), "Wrong input", "The provided amount is not a number") return self.confirm_empty_tokens_dialog = ConfirmationDialog(self, "Empty tokens into another account", "Are you sure you want to empty %d bandwidth tokens " "into another account? " "Warning: one-way action that cannot be revered" % tokens, [ ('EMPTY', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM) ]) self.confirm_empty_tokens_dialog.button_clicked.connect( lambda action2: self.on_confirm_partially_empty_tokens(action2, tokens)) self.confirm_empty_tokens_dialog.show() def on_confirm_partially_empty_tokens(self, action, tokens): self.confirm_empty_tokens_dialog.close_dialog() self.confirm_empty_tokens_dialog = None if action == 0: self.trustchain_request_mgr = TriblerRequestManager() self.trustchain_request_mgr.perform_request("trustchain/bootstrap?amount=%d" % (tokens * MEBIBYTE), self.on_emptying_tokens) def on_emptying_tokens(self, data): if not data: return json_data = json.dumps(data) if has_qr: self.empty_tokens_barcode_dialog = QWidget() self.empty_tokens_barcode_dialog.setWindowTitle("Please scan the following QR code") self.empty_tokens_barcode_dialog.setGeometry(10, 10, 500, 500) qr = qrcode.QRCode( version=1, error_correction=qrcode.constants.ERROR_CORRECT_M, box_size=10, border=5, ) qr.add_data(json_data) qr.make(fit=True) img = qr.make_image() # PIL format qim = ImageQt(img) pixmap = QtGui.QPixmap.fromImage(qim).scaled(600, 600, QtCore.Qt.KeepAspectRatio) label = QLabel(self.empty_tokens_barcode_dialog) label.setPixmap(pixmap) self.empty_tokens_barcode_dialog.resize(pixmap.width(), pixmap.height()) self.empty_tokens_barcode_dialog.show() else: ConfirmationDialog.show_error(self.window(), DEPENDENCY_ERROR_TITLE, DEPENDENCY_ERROR_MESSAGE) def on_channel_autocommit_checkbox_changed(self, _): self.window().gui_settings.setValue("autocommit_enabled", self.window().channel_autocommit_checkbox.isChecked()) def on_family_filter_checkbox_changed(self, _): self.window().gui_settings.setValue("family_filter", self.window().family_filter_checkbox.isChecked()) def on_developer_mode_checkbox_changed(self, _): self.window().gui_settings.setValue("debug", self.window().developer_mode_enabled_checkbox.isChecked()) self.window().left_menu_button_debug.setHidden(not self.window().developer_mode_enabled_checkbox.isChecked()) def on_use_monochrome_icon_checkbox_changed(self, _): use_monochrome_icon = self.window().use_monochrome_icon_checkbox.isChecked() self.window().gui_settings.setValue("use_monochrome_icon", use_monochrome_icon) self.window().update_tray_icon(use_monochrome_icon) def on_anon_download_state_changed(self, _): if self.window().download_settings_anon_checkbox.isChecked(): self.window().download_settings_anon_seeding_checkbox.setChecked(True) self.window().download_settings_anon_seeding_checkbox.setEnabled( not self.window().download_settings_anon_checkbox.isChecked()) def on_choose_download_dir_clicked(self): previous_download_path = self.window().download_location_input.text() or "" download_dir = QFileDialog.getExistingDirectory(self.window(), "Please select the download location", previous_download_path, QFileDialog.ShowDirsOnly) if not download_dir: return self.window().download_location_input.setText(download_dir) def on_choose_watch_dir_clicked(self): if self.window().watchfolder_enabled_checkbox.isChecked(): previous_watch_dir = self.window().watchfolder_location_input.text() or "" watch_dir = QFileDialog.getExistingDirectory(self.window(), "Please select the watch folder", previous_watch_dir, QFileDialog.ShowDirsOnly) if not watch_dir: return self.window().watchfolder_location_input.setText(watch_dir) def on_choose_log_dir_clicked(self): previous_log_dir = self.window().log_location_input.text() or "" log_dir = QFileDialog.getExistingDirectory(self.window(), "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 = "<i>%s</i> is not writable. [%s]" % (log_dir, error) ConfirmationDialog.show_message(self.window(), "Insufficient Permissions", gui_error_message, "OK") else: self.window().log_location_input.setText(log_dir) def initialize_with_settings(self, settings): if not settings: return self.settings = settings settings = settings["settings"] gui_settings = self.window().gui_settings # General settings self.window().family_filter_checkbox.setChecked(get_gui_setting(gui_settings, 'family_filter', True, is_bool=True)) self.window().use_monochrome_icon_checkbox.setChecked(get_gui_setting(gui_settings, "use_monochrome_icon", False, is_bool=True)) self.window().download_location_input.setText(settings['download_defaults']['saveas']) self.window().always_ask_location_checkbox.setChecked( get_gui_setting(gui_settings, "ask_download_settings", True, is_bool=True)) self.window().download_settings_anon_checkbox.setChecked(settings['download_defaults']['anonymity_enabled']) self.window().download_settings_anon_seeding_checkbox.setChecked(settings['download_defaults'][ 'safeseeding_enabled']) self.window().watchfolder_enabled_checkbox.setChecked(settings['watch_folder']['enabled']) self.window().watchfolder_location_input.setText(settings['watch_folder']['directory']) # Channel settings self.window().channel_autocommit_checkbox.setChecked( get_gui_setting(gui_settings, "autocommit_enabled", True, is_bool=True)) # Log directory self.window().log_location_input.setText(settings['general']['log_dir']) # Connection settings self.window().lt_proxy_type_combobox.setCurrentIndex(settings['libtorrent']['proxy_type']) if settings['libtorrent']['proxy_server']: proxy_server = settings['libtorrent']['proxy_server'].split(":") self.window().lt_proxy_server_input.setText(proxy_server[0]) self.window().lt_proxy_port_input.setText(proxy_server[1]) if settings['libtorrent']['proxy_auth']: proxy_auth = settings['libtorrent']['proxy_auth'].split(":") self.window().lt_proxy_username_input.setText(proxy_auth[0]) self.window().lt_proxy_password_input.setText(proxy_auth[1]) self.window().lt_utp_checkbox.setChecked(settings['libtorrent']['utp']) max_conn_download = settings['libtorrent']['max_connections_download'] if max_conn_download == -1: max_conn_download = 0 self.window().max_connections_download_input.setText(str(max_conn_download)) self.window().api_port_input.setText("%s" % get_gui_setting(gui_settings, "api_port", DEFAULT_API_PORT)) # Bandwidth settings self.window().upload_rate_limit_input.setText(str(settings['libtorrent']['max_upload_rate'] // 1024)) self.window().download_rate_limit_input.setText(str(settings['libtorrent']['max_download_rate'] // 1024)) # Seeding settings getattr(self.window(), "seeding_" + settings['download_defaults']['seeding_mode'] + "_radio").setChecked(True) self.window().seeding_time_input.setText(seconds_to_hhmm_string(settings['download_defaults']['seeding_time'])) ind = self.window().seeding_ratio_combobox.findText(str(settings['download_defaults']['seeding_ratio'])) if ind != -1: self.window().seeding_ratio_combobox.setCurrentIndex(ind) # Anonymity settings self.window().allow_exit_node_checkbox.setChecked(settings['tunnel_community']['exitnode_enabled']) self.window().number_hops_slider.setValue(int(settings['download_defaults']['number_hops'])) self.window().number_hops_slider.valueChanged.connect(self.update_anonymity_cost_label) self.update_anonymity_cost_label(int(settings['download_defaults']['number_hops'])) self.window().credit_mining_enabled_checkbox.setChecked(settings['credit_mining']['enabled']) self.window().max_disk_space_input.setText(str(settings['credit_mining']['max_disk_space'])) # Debug self.window().developer_mode_enabled_checkbox.setChecked(get_gui_setting(gui_settings, "debug", False, is_bool=True)) self.window().checkbox_enable_resource_log.setChecked(settings['resource_monitor']['enabled']) cpu_priority = 1 if 'cpu_priority' in settings['resource_monitor']: cpu_priority = int(settings['resource_monitor']['cpu_priority']) self.window().slider_cpu_level.setValue(cpu_priority) self.window().cpu_priority_value.setText("Current Priority = %s" % cpu_priority) self.window().slider_cpu_level.valueChanged.connect(self.show_updated_cpu_priority) self.window().checkbox_enable_network_statistics.setChecked(settings['ipv8']['statistics']) def update_anonymity_cost_label(self, value): html_text = """<html><head/><body><p>Download with <b>%d</b> hop(s) of anonymity. When you download a file of 200 Megabyte, you will pay roughly <b>%d</b> Megabyte of bandwidth tokens.</p></body></html> """ % (value, 400 * (value - 1) + 200) self.window().anonymity_costs_label.setText(html_text) def show_updated_cpu_priority(self, value): self.window().cpu_priority_value.setText("Current Priority = %s" % value) def load_settings(self): self.settings_request_mgr = TriblerRequestManager() self.settings_request_mgr.perform_request("settings", self.initialize_with_settings) def clicked_tab_button(self, tab_button_name): if tab_button_name == "settings_general_button": self.window().settings_stacked_widget.setCurrentIndex(PAGE_SETTINGS_GENERAL) elif tab_button_name == "settings_connection_button": self.window().settings_stacked_widget.setCurrentIndex(PAGE_SETTINGS_CONNECTION) elif tab_button_name == "settings_bandwidth_button": self.window().settings_stacked_widget.setCurrentIndex(PAGE_SETTINGS_BANDWIDTH) elif tab_button_name == "settings_seeding_button": self.window().settings_stacked_widget.setCurrentIndex(PAGE_SETTINGS_SEEDING) elif tab_button_name == "settings_anonymity_button": self.window().settings_stacked_widget.setCurrentIndex(PAGE_SETTINGS_ANONYMITY) elif tab_button_name == "settings_debug_button": self.window().settings_stacked_widget.setCurrentIndex(PAGE_SETTINGS_DEBUG) self.update_stacked_widget_height() def update_stacked_widget_height(self): """ Update the height of the settings tab. This is required since the height of a QStackedWidget is by default the height of the largest page. This messes up the scroll bar. """ for index in range(self.window().settings_stacked_widget.count()): if index == self.window().settings_stacked_widget.currentIndex(): self.window().settings_stacked_widget.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) else: self.window().settings_stacked_widget.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) self.window().settings_stacked_widget.adjustSize() def save_settings(self): # Create a dictionary with all available settings settings_data = {'general': {}, 'Tribler': {}, 'download_defaults': {}, 'libtorrent': {}, 'watch_folder': {}, 'tunnel_community': {}, 'trustchain': {}, 'credit_mining': {}, 'resource_monitor': {}, 'ipv8': {}, 'chant': {}} settings_data['download_defaults']['saveas'] = self.window().download_location_input.text().encode('utf-8') settings_data['general']['log_dir'] = self.window().log_location_input.text() settings_data['watch_folder']['enabled'] = self.window().watchfolder_enabled_checkbox.isChecked() if settings_data['watch_folder']['enabled']: settings_data['watch_folder']['directory'] = self.window().watchfolder_location_input.text() settings_data['libtorrent']['proxy_type'] = self.window().lt_proxy_type_combobox.currentIndex() if self.window().lt_proxy_server_input.text() and len(self.window().lt_proxy_server_input.text()) > 0 and len( self.window().lt_proxy_port_input.text()) > 0: try: settings_data['libtorrent']['proxy_server'] = "%s:%s" % (self.window().lt_proxy_server_input.text(), int(self.window().lt_proxy_port_input.text())) except ValueError: ConfirmationDialog.show_error(self.window(), "Invalid proxy port number", "You've entered an invalid format for the proxy port number. " "Please enter a whole number.") return else: settings_data['libtorrent']['proxy_server'] = ":" if self.window().lt_proxy_username_input.text() and self.window().lt_proxy_password_input.text(): settings_data['libtorrent']['proxy_auth'] = "%s:%s" % (self.window().lt_proxy_username_input.text(), self.window().lt_proxy_password_input.text()) else: settings_data['libtorrent']['proxy_auth'] = ":" settings_data['libtorrent']['utp'] = self.window().lt_utp_checkbox.isChecked() try: max_conn_download = int(self.window().max_connections_download_input.text()) except ValueError: ConfirmationDialog.show_error(self.window(), "Invalid number of connections", "You've entered an invalid format for the maximum number of connections. " "Please enter a whole number.") return if max_conn_download == 0: max_conn_download = -1 settings_data['libtorrent']['max_connections_download'] = max_conn_download try: if self.window().upload_rate_limit_input.text(): user_upload_rate_limit = int(self.window().upload_rate_limit_input.text()) * 1024 if user_upload_rate_limit < sys.maxsize: settings_data['libtorrent']['max_upload_rate'] = user_upload_rate_limit else: raise ValueError if self.window().download_rate_limit_input.text(): user_download_rate_limit = int(self.window().download_rate_limit_input.text()) * 1024 if user_download_rate_limit < sys.maxsize: settings_data['libtorrent']['max_download_rate'] = user_download_rate_limit else: raise ValueError except ValueError: ConfirmationDialog.show_error(self.window(), "Invalid value for bandwidth limit", "You've entered an invalid value for the maximum upload/download rate. " "Please enter a whole number (max: %d)" % (sys.maxsize / 1000)) return try: if self.window().api_port_input.text(): api_port = int(self.window().api_port_input.text()) if api_port <= 0 or api_port >= 65536: raise ValueError() self.window().gui_settings.setValue("api_port", api_port) except ValueError: ConfirmationDialog.show_error(self.window(), "Invalid value for api port", "Please enter a valid port for the api (between 0 and 65536)") return seeding_modes = ['forever', 'time', 'never', 'ratio'] selected_mode = 'forever' for seeding_mode in seeding_modes: if getattr(self.window(), "seeding_" + seeding_mode + "_radio").isChecked(): selected_mode = seeding_mode break settings_data['download_defaults']['seeding_mode'] = selected_mode settings_data['download_defaults']['seeding_ratio'] = self.window().seeding_ratio_combobox.currentText() try: settings_data['download_defaults']['seeding_time'] = string_to_seconds( self.window().seeding_time_input.text()) except ValueError: ConfirmationDialog.show_error(self.window(), "Invalid seeding time", "You've entered an invalid format for the seeding time (expected HH:MM)") return settings_data['credit_mining']['enabled'] = self.window().credit_mining_enabled_checkbox.isChecked() try: settings_data['credit_mining']['max_disk_space'] = int(self.window().max_disk_space_input.text()) except ValueError: ConfirmationDialog.show_error(self.window(), "Invalid number", "You've entered an invalid number for max disk space value") return settings_data['tunnel_community']['exitnode_enabled'] = self.window().allow_exit_node_checkbox.isChecked() settings_data['download_defaults']['number_hops'] = self.window().number_hops_slider.value() settings_data['download_defaults']['anonymity_enabled'] = \ self.window().download_settings_anon_checkbox.isChecked() settings_data['download_defaults']['safeseeding_enabled'] = \ self.window().download_settings_anon_seeding_checkbox.isChecked() settings_data['resource_monitor']['enabled'] = self.window().checkbox_enable_resource_log.isChecked() settings_data['resource_monitor']['cpu_priority'] = int(self.window().slider_cpu_level.value()) # network statistics settings_data['ipv8']['statistics'] = self.window().checkbox_enable_network_statistics.isChecked() self.window().settings_save_button.setEnabled(False) self.settings_request_mgr = TriblerRequestManager() self.settings_request_mgr.perform_request("settings", self.on_settings_saved, method='POST', raw_data=json.dumps(settings_data)) 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, "Settings saved", "Your settings have been saved.", [('CLOSE', BUTTON_TYPE_NORMAL)]) self.saved_dialog.button_clicked.connect(self.on_dialog_cancel_clicked) self.saved_dialog.show() self.window().fetch_settings() def on_dialog_cancel_clicked(self, _): self.window().settings_save_button.setEnabled(True) self.saved_dialog.close_dialog() self.saved_dialog = None
class TriblerWindow(QMainWindow): resize_event = pyqtSignal() escape_pressed = pyqtSignal() tribler_crashed = pyqtSignal(str) received_search_completions = pyqtSignal(object) def on_exception(self, *exc_info): if self.exception_handler_called: # We only show one feedback dialog, even when there are two consecutive exceptions. return self.exception_handler_called = True exception_text = "".join(traceback.format_exception(*exc_info)) logging.error(exception_text) self.tribler_crashed.emit(exception_text) self.delete_tray_icon() # Stop the download loop self.downloads_page.stop_loading_downloads() # Add info about whether we are stopping Tribler or not os.environ['TRIBLER_SHUTTING_DOWN'] = str(self.core_manager.shutting_down) if not self.core_manager.shutting_down: self.core_manager.stop(stop_app_on_shutdown=False) self.setHidden(True) if self.debug_window: self.debug_window.setHidden(True) dialog = FeedbackDialog(self, exception_text, self.core_manager.events_manager.tribler_version, self.start_time) dialog.show() def __init__(self, core_args=None, core_env=None, api_port=None): QMainWindow.__init__(self) QCoreApplication.setOrganizationDomain("nl") QCoreApplication.setOrganizationName("TUDelft") QCoreApplication.setApplicationName("Tribler") QCoreApplication.setAttribute(Qt.AA_UseHighDpiPixmaps) self.gui_settings = QSettings() api_port = api_port or int(get_gui_setting(self.gui_settings, "api_port", DEFAULT_API_PORT)) dispatcher.update_worker_settings(port=api_port) self.navigation_stack = [] self.tribler_started = False self.tribler_settings = None self.debug_window = None self.core_manager = CoreManager(api_port) self.pending_requests = {} self.pending_uri_requests = [] self.download_uri = None self.dialog = None self.new_version_dialog = None self.start_download_dialog_active = False self.request_mgr = None self.search_request_mgr = None self.search_suggestion_mgr = None self.selected_torrent_files = [] self.vlc_available = True self.has_search_results = False self.last_search_query = None self.last_search_time = None self.start_time = time.time() self.exception_handler_called = False self.token_refresh_timer = None self.shutdown_timer = None self.add_torrent_url_dialog_active = False sys.excepthook = self.on_exception uic.loadUi(get_ui_file_path('mainwindow.ui'), self) TriblerRequestManager.window = self self.tribler_status_bar.hide() self.token_balance_widget.mouseReleaseEvent = self.on_token_balance_click def on_state_update(new_state): self.loading_text_label.setText(new_state) self.core_manager.core_state_update.connect(on_state_update) self.magnet_handler = MagnetHandler(self.window) QDesktopServices.setUrlHandler("magnet", self.magnet_handler, "on_open_magnet_link") self.debug_pane_shortcut = QShortcut(QKeySequence("Ctrl+d"), self) self.debug_pane_shortcut.activated.connect(self.clicked_menu_button_debug) self.import_torrent_shortcut = QShortcut(QKeySequence("Ctrl+o"), self) self.import_torrent_shortcut.activated.connect(self.on_add_torrent_browse_file) self.add_torrent_url_shortcut = QShortcut(QKeySequence("Ctrl+i"), self) self.add_torrent_url_shortcut.activated.connect(self.on_add_torrent_from_url) # Remove the focus rect on OS X for widget in self.findChildren(QLineEdit) + self.findChildren(QListWidget) + self.findChildren(QTreeWidget): widget.setAttribute(Qt.WA_MacShowFocusRect, 0) self.menu_buttons = [self.left_menu_button_home, self.left_menu_button_search, self.left_menu_button_my_channel, self.left_menu_button_subscriptions, self.left_menu_button_video_player, self.left_menu_button_downloads, self.left_menu_button_discovered] self.video_player_page.initialize_player() self.search_results_page.initialize_search_results_page(self.gui_settings) self.settings_page.initialize_settings_page() self.subscribed_channels_page.initialize() self.edit_channel_page.initialize_edit_channel_page(self.gui_settings) self.downloads_page.initialize_downloads_page() self.home_page.initialize_home_page() self.loading_page.initialize_loading_page() self.discovering_page.initialize_discovering_page() self.discovered_page.initialize_discovered_page(self.gui_settings) self.channel_page.initialize_channel_page(self.gui_settings) self.trust_page.initialize_trust_page() self.token_mining_page.initialize_token_mining_page() self.stackedWidget.setCurrentIndex(PAGE_LOADING) # Create the system tray icon if QSystemTrayIcon.isSystemTrayAvailable(): self.tray_icon = QSystemTrayIcon() use_monochrome_icon = get_gui_setting(self.gui_settings, "use_monochrome_icon", False, is_bool=True) self.update_tray_icon(use_monochrome_icon) # Create the tray icon menu menu = self.create_add_torrent_menu() show_downloads_action = QAction('Show downloads', self) show_downloads_action.triggered.connect(self.clicked_menu_button_downloads) token_balance_action = QAction('Show token balance', self) token_balance_action.triggered.connect(lambda: self.on_token_balance_click(None)) quit_action = QAction('Quit Tribler', self) quit_action.triggered.connect(self.close_tribler) menu.addSeparator() menu.addAction(show_downloads_action) menu.addAction(token_balance_action) menu.addSeparator() menu.addAction(quit_action) self.tray_icon.setContextMenu(menu) else: self.tray_icon = None self.hide_left_menu_playlist() self.left_menu_button_debug.setHidden(True) self.top_menu_button.setHidden(True) self.left_menu.setHidden(True) self.token_balance_widget.setHidden(True) self.settings_button.setHidden(True) self.add_torrent_button.setHidden(True) self.top_search_bar.setHidden(True) # Set various icons self.top_menu_button.setIcon(QIcon(get_image_path('menu.png'))) self.search_completion_model = QStringListModel() completer = QCompleter() completer.setModel(self.search_completion_model) completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion) self.item_delegate = QStyledItemDelegate() completer.popup().setItemDelegate(self.item_delegate) completer.popup().setStyleSheet(""" QListView { background-color: #404040; } QListView::item { color: #D0D0D0; padding-top: 5px; padding-bottom: 5px; } QListView::item:hover { background-color: #707070; } """) self.top_search_bar.setCompleter(completer) # Toggle debug if developer mode is enabled self.window().left_menu_button_debug.setHidden( not get_gui_setting(self.gui_settings, "debug", False, is_bool=True)) # Start Tribler self.core_manager.start(core_args=core_args, core_env=core_env) self.core_manager.events_manager.torrent_finished.connect(self.on_torrent_finished) self.core_manager.events_manager.new_version_available.connect(self.on_new_version_available) self.core_manager.events_manager.tribler_started.connect(self.on_tribler_started) self.core_manager.events_manager.events_started.connect(self.on_events_started) self.core_manager.events_manager.low_storage_signal.connect(self.on_low_storage) self.core_manager.events_manager.credit_mining_signal.connect(self.on_credit_mining_error) self.core_manager.events_manager.tribler_shutdown_signal.connect(self.on_tribler_shutdown_state_update) self.core_manager.events_manager.upgrader_tick.connect( lambda text: self.show_status_bar("Upgrading Tribler database: " + text)) self.core_manager.events_manager.upgrader_finished.connect( lambda _: self.hide_status_bar()) self.core_manager.events_manager.received_search_result.connect( self.search_results_page.received_search_result) # Install signal handler for ctrl+c events def sigint_handler(*_): self.close_tribler() signal.signal(signal.SIGINT, sigint_handler) self.installEventFilter(self.video_player_page) # Resize the window according to the settings center = QApplication.desktop().availableGeometry(self).center() pos = self.gui_settings.value("pos", QPoint(center.x() - self.width() * 0.5, center.y() - self.height() * 0.5)) size = self.gui_settings.value("size", self.size()) self.move(pos) self.resize(size) self.show() def update_tray_icon(self, use_monochrome_icon): if not QSystemTrayIcon.isSystemTrayAvailable() or not self.tray_icon: return if use_monochrome_icon: self.tray_icon.setIcon(QIcon(QPixmap(get_image_path('monochrome_tribler.png')))) else: self.tray_icon.setIcon(QIcon(QPixmap(get_image_path('tribler.png')))) self.tray_icon.show() def delete_tray_icon(self): if self.tray_icon: try: self.tray_icon.deleteLater() except RuntimeError: # The tray icon might have already been removed when unloading Qt. # This is due to the C code actually being asynchronous. logging.debug("Tray icon already removed, no further deletion necessary.") self.tray_icon = None 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: """ self.downloads_page.stop_loading_downloads() self.core_manager.stop(False) close_dialog = ConfirmationDialog(self.window(), "<b>CRITICAL ERROR</b>", "You are running low on disk space (<100MB). Please make sure to have " "sufficient free space available and restart Tribler again.", [("Close Tribler", BUTTON_TYPE_NORMAL)]) close_dialog.button_clicked.connect(lambda _: self.close_tribler()) close_dialog.show() def on_torrent_finished(self, torrent_info): self.tray_show_message("Download finished", "Download of %s has finished." % torrent_info["name"]) def show_loading_screen(self): self.top_menu_button.setHidden(True) self.left_menu.setHidden(True) self.token_balance_widget.setHidden(True) self.settings_button.setHidden(True) self.add_torrent_button.setHidden(True) self.top_search_bar.setHidden(True) self.stackedWidget.setCurrentIndex(PAGE_LOADING) 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.tray_icon: try: self.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.tray_icon: try: self.tray_icon.showMessage(title, message) except RuntimeError as e: logging.error("Failed to set tray message: %s", str(e)) def on_tribler_started(self): self.tribler_started = True 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) # fetch the settings, needed for the video player port self.request_mgr = TriblerRequestManager() self.fetch_settings() self.downloads_page.start_loading_downloads() self.home_page.load_popular_torrents() if not self.gui_settings.value("first_discover", False) and not self.core_manager.use_existing_core: self.window().gui_settings.setValue("first_discover", True) self.discovering_page.is_discovering = True self.stackedWidget.setCurrentIndex(PAGE_DISCOVERING) else: self.clicked_menu_button_home() self.setAcceptDrops(True) def on_events_started(self, json_dict): self.setWindowTitle("Tribler %s" % json_dict["version"]) def show_status_bar(self, message): self.tribler_status_bar_label.setText(message) self.tribler_status_bar.show() def hide_status_bar(self): self.tribler_status_bar.hide() def process_uri_request(self): """ Process a URI request if we have one in the queue. """ if len(self.pending_uri_requests) == 0: return uri = self.pending_uri_requests.pop() if uri.startswith('file') or uri.startswith('magnet'): self.start_download_from_uri(uri) def perform_start_download_request(self, uri, anon_download, safe_seeding, destination, selected_files, total_files=0, callback=None): # Check if destination directory is writable is_writable, error = is_dir_writable(destination) if not is_writable: gui_error_message = "Insufficient write permissions to <i>%s</i> directory. Please add proper " \ "write permissions on the directory and add the torrent again. %s" \ % (destination, error) ConfirmationDialog.show_message(self.window(), "Download error <i>%s</i>" % uri, gui_error_message, "OK") return selected_files_list = [] if len(selected_files) != total_files: # Not all files included selected_files_list = [filename for filename in selected_files] anon_hops = int(self.tribler_settings['download_defaults']['number_hops']) if anon_download else 0 safe_seeding = 1 if safe_seeding else 0 post_data = { "uri": uri, "anon_hops": anon_hops, "safe_seeding": safe_seeding, "destination": destination, "selected_files": selected_files_list } request_mgr = TriblerRequestManager() request_mgr.perform_request("downloads", callback if callback else self.on_download_added, method='PUT', data=post_data) # Save the download location to the GUI settings current_settings = get_gui_setting(self.gui_settings, "recent_download_locations", "") recent_locations = current_settings.split(",") if len(current_settings) > 0 else [] if isinstance(destination, six.text_type): destination = destination.encode('utf-8') encoded_destination = hexlify(destination) if encoded_destination in recent_locations: recent_locations.remove(encoded_destination) recent_locations.insert(0, encoded_destination) if len(recent_locations) > 5: recent_locations = recent_locations[:5] self.gui_settings.setValue("recent_download_locations", ','.join(recent_locations)) def on_new_version_available(self, version): if version == str(self.gui_settings.value('last_reported_version')): return self.new_version_dialog = ConfirmationDialog(self, "New version available", "Version %s of Tribler is available.Do you want to visit the " "website to download the newest version?" % version, [('IGNORE', BUTTON_TYPE_NORMAL), ('LATER', BUTTON_TYPE_NORMAL), ('OK', BUTTON_TYPE_NORMAL)]) self.new_version_dialog.button_clicked.connect(lambda action: self.on_new_version_dialog_done(version, action)) self.new_version_dialog.show() def on_new_version_dialog_done(self, version, action): if action == 0: # ignore self.gui_settings.setValue("last_reported_version", version) elif action == 2: # ok import webbrowser webbrowser.open("https://tribler.org") if self.new_version_dialog: self.new_version_dialog.close_dialog() self.new_version_dialog = None def on_search_text_change(self, text): self.search_suggestion_mgr = TriblerRequestManager() self.search_suggestion_mgr.perform_request( "search/completions", self.on_received_search_completions, url_params={'q': sanitize_for_fts(text)}) def on_received_search_completions(self, completions): if completions is None: return self.received_search_completions.emit(completions) self.search_completion_model.setStringList(completions["completions"]) def fetch_settings(self): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("settings", self.received_settings, capture_errors=False) def received_settings(self, settings): if not settings: return # If we cannot receive the settings, stop Tribler with an option to send the crash report. if 'error' in settings: raise RuntimeError(TriblerRequestManager.get_message_from_error(settings)) self.tribler_settings = settings['settings'] # Set the video server port self.video_player_page.video_player_port = settings["ports"]["video_server~port"] # Disable various components based on the settings if not self.tribler_settings['video_server']['enabled']: self.left_menu_button_video_player.setHidden(True) self.downloads_creditmining_button.setHidden(not self.tribler_settings["credit_mining"]["enabled"]) self.downloads_all_button.click() # process pending file requests (i.e. someone clicked a torrent file when Tribler was closed) # We do this after receiving the settings so we have the default download location. self.process_uri_request() # Set token balance refresh timer and load the token balance self.token_refresh_timer = QTimer() self.token_refresh_timer.timeout.connect(self.load_token_balance) self.token_refresh_timer.start(60000) self.load_token_balance() def on_top_search_button_click(self): current_ts = time.time() current_search_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 self.left_menu_button_search.setChecked(True) self.has_search_results = True self.clicked_menu_button_search() self.search_results_page.perform_search(current_search_query) self.last_search_query = current_search_query self.last_search_time = current_ts def on_settings_button_click(self): self.deselect_all_menu_buttons() self.stackedWidget.setCurrentIndex(PAGE_SETTINGS) self.settings_page.load_settings() self.navigation_stack = [] self.hide_left_menu_playlist() def on_token_balance_click(self, _): self.raise_window() self.deselect_all_menu_buttons() self.stackedWidget.setCurrentIndex(PAGE_TRUST) self.load_token_balance() self.trust_page.load_blocks() self.navigation_stack = [] self.hide_left_menu_playlist() def load_token_balance(self): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("trustchain/statistics", self.received_trustchain_statistics, capture_errors=False) def received_trustchain_statistics(self, statistics): if not statistics or "statistics" not in statistics: return self.trust_page.received_trustchain_statistics(statistics) statistics = statistics["statistics"] if 'latest_block' in statistics: balance = (statistics["latest_block"]["transaction"]["total_up"] - statistics["latest_block"]["transaction"]["total_down"]) self.set_token_balance(balance) else: self.token_balance_label.setText("0 MB") # If trust page is currently visible, then load the graph as well if self.stackedWidget.currentIndex() == PAGE_TRUST: self.trust_page.load_blocks() def set_token_balance(self, balance): if abs(balance) > 1024 ** 4: # Balance is over a TB balance /= 1024.0 ** 4 self.token_balance_label.setText("%.1f TB" % balance) elif abs(balance) > 1024 ** 3: # Balance is over a GB balance /= 1024.0 ** 3 self.token_balance_label.setText("%.1f GB" % balance) else: balance /= 1024.0 ** 2 self.token_balance_label.setText("%d MB" % balance) def raise_window(self): self.setWindowState(self.windowState() & ~Qt.WindowMinimized | Qt.WindowActive) self.raise_() self.activateWindow() def create_add_torrent_menu(self): """ Create a menu to add new torrents. Shows when users click on the tray icon or the big plus button. """ menu = TriblerActionMenu(self) browse_files_action = QAction('Import torrent from file', self) browse_directory_action = QAction('Import torrent(s) from directory', self) add_url_action = QAction('Import torrent from magnet/URL', self) add_mdblob_action = QAction('Import Tribler metadata from file', self) browse_files_action.triggered.connect(self.on_add_torrent_browse_file) browse_directory_action.triggered.connect(self.on_add_torrent_browse_dir) add_url_action.triggered.connect(self.on_add_torrent_from_url) add_mdblob_action.triggered.connect(self.on_add_mdblob_browse_file) menu.addAction(browse_files_action) menu.addAction(browse_directory_action) menu.addAction(add_url_action) menu.addAction(add_mdblob_action) return menu def on_add_torrent_button_click(self, pos): self.create_add_torrent_menu().exec_(self.mapToGlobal(self.add_torrent_button.pos())) def on_add_torrent_browse_file(self): filenames = QFileDialog.getOpenFileNames(self, "Please select the .torrent file", QDir.homePath(), "Torrent files (*.torrent)") if len(filenames[0]) > 0: [self.pending_uri_requests.append(u"file:%s" % filename) for filename in filenames[0]] self.process_uri_request() def on_add_mdblob_browse_file(self): filenames = QFileDialog.getOpenFileNames(self, "Please select the .mdblob file", QDir.homePath(), "Tribler metadata files (*.mdblob)") if len(filenames[0]) > 0: for filename in filenames[0]: self.pending_uri_requests.append(u"file:%s" % filename) self.process_uri_request() def start_download_from_uri(self, uri): self.download_uri = uri if get_gui_setting(self.gui_settings, "ask_download_settings", True, is_bool=True): # If tribler settings is not available, fetch the settings and inform the user to try again. if not self.tribler_settings: self.fetch_settings() ConfirmationDialog.show_error(self, "Download Error", "Tribler settings is not available yet. " "Fetching it now. Please try again later.") return # Clear any previous dialog if exists if self.dialog: self.dialog.close_dialog() self.dialog = None self.dialog = StartDownloadDialog(self, self.download_uri) self.dialog.button_clicked.connect(self.on_start_download_action) self.dialog.show() self.start_download_dialog_active = True else: # 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'], [], 0) self.process_uri_request() 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()) else: ConfirmationDialog.show_error(self, "Tribler UI Error", "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_add_torrent_browse_dir(self): chosen_dir = QFileDialog.getExistingDirectory(self, "Please select the directory containing the .torrent files", QDir.homePath(), QFileDialog.ShowDirsOnly) if len(chosen_dir) != 0: self.selected_torrent_files = [torrent_file for torrent_file in glob.glob(chosen_dir + "/*.torrent")] self.dialog = ConfirmationDialog(self, "Add torrents from directory", "Are you sure you want to add %d torrents to Tribler?" % len(self.selected_torrent_files), [('ADD', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)]) self.dialog.button_clicked.connect(self.on_confirm_add_directory_dialog) self.dialog.show() def on_confirm_add_directory_dialog(self, action): if action == 0: for torrent_file in self.selected_torrent_files: escaped_uri = u"file:%s" % pathname2url(torrent_file.encode('utf-8')) self.perform_start_download_request(escaped_uri, self.window().tribler_settings['download_defaults'][ 'anonymity_enabled'], self.window().tribler_settings['download_defaults'][ 'safeseeding_enabled'], self.tribler_settings['download_defaults']['saveas'], [], 0) if self.dialog: self.dialog.close_dialog() self.dialog = None def on_add_torrent_from_url(self): # Make sure that the window is visible (this action might be triggered from the tray icon) self.raise_window() if self.video_player_page.isVisible(): # If we're adding a torrent from the video player page, go to the home page. # This is necessary since VLC takes the screen and the popup becomes invisible. self.clicked_menu_button_home() if not self.add_torrent_url_dialog_active: self.dialog = ConfirmationDialog(self, "Add torrent from URL/magnet link", "Please enter the URL/magnet link in the field below:", [('ADD', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)], show_input=True) self.dialog.dialog_widget.dialog_input.setPlaceholderText('URL/magnet link') self.dialog.dialog_widget.dialog_input.setFocus() self.dialog.button_clicked.connect(self.on_torrent_from_url_dialog_done) self.dialog.show() self.add_torrent_url_dialog_active = True def on_torrent_from_url_dialog_done(self, action): self.add_torrent_url_dialog_active = False if self.dialog and self.dialog.dialog_widget: uri = self.dialog.dialog_widget.dialog_input.text().strip() # If the URI is a 40-bytes hex-encoded infohash, convert it to a valid magnet link if len(uri) == 40: valid_ih_hex = True try: int(uri, 16) except ValueError: valid_ih_hex = False if valid_ih_hex: uri = "magnet:?xt=urn:btih:" + uri # Remove first dialog self.dialog.close_dialog() self.dialog = None if action == 0: self.start_download_from_uri(uri) def on_download_added(self, result): if not result: return if len(self.pending_uri_requests) == 0: # Otherwise, we first process the remaining requests. self.window().left_menu_button_downloads.click() else: self.process_uri_request() def on_top_menu_button_click(self): if self.left_menu.isHidden(): self.left_menu.show() else: self.left_menu.hide() def deselect_all_menu_buttons(self, except_select=None): for button in self.menu_buttons: if button == except_select: button.setEnabled(False) continue button.setEnabled(True) if button == self.left_menu_button_search and not self.has_search_results: button.setEnabled(False) button.setChecked(False) def clicked_menu_button_home(self): self.deselect_all_menu_buttons(self.left_menu_button_home) self.stackedWidget.setCurrentIndex(PAGE_HOME) self.navigation_stack = [] self.hide_left_menu_playlist() def clicked_menu_button_search(self): self.deselect_all_menu_buttons(self.left_menu_button_search) self.stackedWidget.setCurrentIndex(PAGE_SEARCH_RESULTS) self.navigation_stack = [] self.hide_left_menu_playlist() def clicked_menu_button_discovered(self): self.deselect_all_menu_buttons(self.left_menu_button_discovered) self.stackedWidget.setCurrentIndex(PAGE_DISCOVERED) self.discovered_page.load_discovered_channels() self.discovered_channels_list.setFocus() self.navigation_stack = [] self.hide_left_menu_playlist() def clicked_menu_button_my_channel(self): self.deselect_all_menu_buttons(self.left_menu_button_my_channel) self.stackedWidget.setCurrentIndex(PAGE_EDIT_CHANNEL) self.edit_channel_page.load_my_channel_overview() self.navigation_stack = [] self.hide_left_menu_playlist() def clicked_menu_button_video_player(self): self.deselect_all_menu_buttons(self.left_menu_button_video_player) self.stackedWidget.setCurrentIndex(PAGE_VIDEO_PLAYER) self.navigation_stack = [] self.show_left_menu_playlist() def clicked_menu_button_downloads(self): self.deselect_all_menu_buttons(self.left_menu_button_downloads) self.raise_window() self.left_menu_button_downloads.setChecked(True) self.stackedWidget.setCurrentIndex(PAGE_DOWNLOADS) self.navigation_stack = [] self.hide_left_menu_playlist() def clicked_menu_button_debug(self): if not self.debug_window: self.debug_window = DebugWindow(self.tribler_settings, self.core_manager.events_manager.tribler_version) self.debug_window.show() def clicked_menu_button_subscriptions(self): self.deselect_all_menu_buttons(self.left_menu_button_subscriptions) self.stackedWidget.setCurrentIndex(PAGE_SUBSCRIBED_CHANNELS) self.subscribed_channels_page.load_subscribed_channels() self.navigation_stack = [] self.hide_left_menu_playlist() def hide_left_menu_playlist(self): self.left_menu_seperator.setHidden(True) self.left_menu_playlist_label.setHidden(True) self.left_menu_playlist.setHidden(True) def show_left_menu_playlist(self): self.left_menu_seperator.setHidden(False) self.left_menu_playlist_label.setHidden(False) self.left_menu_playlist.setHidden(False) def on_channel_clicked(self, channel_info): self.channel_page.initialize_with_channel(channel_info) self.navigation_stack.append(self.stackedWidget.currentIndex()) self.stackedWidget.setCurrentIndex(PAGE_CHANNEL_DETAILS) def on_page_back_clicked(self): try: prev_page = self.navigation_stack.pop() self.stackedWidget.setCurrentIndex(prev_page) except IndexError: logging.exception("Unknown page found in stack") def on_credit_mining_error(self, error): ConfirmationDialog.show_error(self, "Credit Mining Error", error[u'message']) def on_edit_channel_clicked(self): self.stackedWidget.setCurrentIndex(PAGE_EDIT_CHANNEL) self.navigation_stack = [] self.channel_page.on_edit_channel_clicked() def resizeEvent(self, _): # Resize home page cells cell_width = self.home_page_table_view.width() / 3 - 3 # We have some padding to the right max_height = self.home_page_table_view.height() / 3 - 4 cell_height = min(cell_width / 2 + 60, max_height) for i in range(0, 3): self.home_page_table_view.setColumnWidth(i, cell_width) self.home_page_table_view.setRowHeight(i, cell_height) self.resize_event.emit() def exit_full_screen(self): self.top_bar.show() self.left_menu.show() self.video_player_page.is_full_screen = False self.showNormal() def close_tribler(self): if not self.core_manager.shutting_down: 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("Shutting down...") if self.debug_window: self.debug_window.setHidden(True) self.shutdown_timer = QTimer() self.shutdown_timer.timeout.connect(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_queue.clear() # Stop the token balance timer if self.token_refresh_timer: self.token_refresh_timer.stop() def closeEvent(self, close_event): self.close_tribler() close_event.ignore() def keyReleaseEvent(self, event): if event.key() == Qt.Key_Escape: self.escape_pressed.emit() if self.isFullScreen(): self.exit_full_screen() def dragEnterEvent(self, e): file_urls = [_qurl_to_path(url) for url in e.mimeData().urls()] if e.mimeData().hasUrls() else [] if any(os.path.isfile(filename) for filename in file_urls): e.accept() else: e.ignore() def dropEvent(self, e): file_urls = ([(_qurl_to_path(url), url.toString()) for url in e.mimeData().urls()] if e.mimeData().hasUrls() else []) for filename, fileurl in file_urls: if os.path.isfile(filename): self.start_download_from_uri(fileurl) e.accept() def clicked_force_shutdown(self): process_checker = ProcessChecker() if process_checker.already_running: core_pid = process_checker.get_pid_from_lock_file() os.kill(int(core_pid), 9) # Stop the Qt application QApplication.quit() def on_tribler_shutdown_state_update(self, state): self.loading_text_label.setText(state)
class EditChannelPage(QWidget): """ This class is responsible for managing lists and data on your channel page, including torrents, playlists and rss feeds. """ playlists_loaded = pyqtSignal(object) def __init__(self): QWidget.__init__(self) self.remove_torrent_requests = [] self.channel_overview = None self.playlists = None self.editing_playlist = None self.viewing_playlist = None self.dialog = None self.editchannel_request_mgr = None def initialize_edit_channel_page(self): self.window().create_channel_intro_button.clicked.connect(self.on_create_channel_intro_button_clicked) self.window().create_channel_form.hide() self.window().edit_channel_stacked_widget.setCurrentIndex(1) self.window().edit_channel_details_stacked_widget.setCurrentIndex(PAGE_EDIT_CHANNEL_OVERVIEW) self.window().create_channel_button.clicked.connect(self.on_create_channel_button_pressed) self.window().edit_channel_save_button.clicked.connect(self.on_edit_channel_save_button_pressed) self.window().edit_channel_torrents_remove_selected_button.clicked.connect( self.on_torrents_remove_selected_clicked) self.window().edit_channel_torrents_remove_all_button.clicked.connect(self.on_torrents_remove_all_clicked) self.window().edit_channel_torrents_add_button.clicked.connect(self.on_torrents_add_clicked) self.window().edit_channel_details_playlist_manage.playlist_saved.connect(self.load_channel_playlists) self.window().edit_channel_playlist_torrents_back.clicked.connect(self.on_playlist_torrents_back_clicked) self.window().edit_channel_playlists_list.itemClicked.connect(self.on_playlist_item_clicked) self.window().edit_channel_playlist_manage_torrents_button.clicked.connect(self.on_playlist_manage_clicked) self.window().edit_channel_create_playlist_button.clicked.connect(self.on_playlist_created_clicked) self.window().playlist_edit_save_button.clicked.connect(self.on_playlist_edit_save_clicked) self.window().playlist_edit_cancel_button.clicked.connect(self.on_playlist_edit_cancel_clicked) self.window().edit_channel_details_rss_feeds_remove_selected_button.clicked.connect( self.on_rss_feeds_remove_selected_clicked) self.window().edit_channel_details_rss_add_button.clicked.connect(self.on_rss_feed_add_clicked) self.window().edit_channel_details_rss_refresh_button.clicked.connect(self.on_rss_feeds_refresh_clicked) # Tab bar buttons self.window().channel_settings_tab.initialize() self.window().channel_settings_tab.clicked_tab_button.connect(self.clicked_tab_button) # Chant publish widget is hidden by default and only shown when necessary self.window().dirty_channel_widget.setHidden(True) self.window().edit_channel_commit_button.clicked.connect(self.clicked_edit_channel_commit_button) self.window().export_channel_button.clicked.connect(self.on_export_mdblob) self.window().export_channel_button.setHidden(True) def load_my_channel_overview(self): if not self.channel_overview: self.window().edit_channel_stacked_widget.setCurrentIndex(2) self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request("mychannel", self.initialize_with_channel_overview, capture_errors=False) def initialize_with_channel_overview(self, overview): if not overview: return if 'error' in overview: self.window().edit_channel_stacked_widget.setCurrentIndex(0) return self.channel_overview = overview["mychannel"] if "chant" in self.channel_overview: self.window().edit_channel_playlists_button.setHidden(True) self.window().edit_channel_rss_feeds_button.setHidden(True) self.window().label_7.setText(chant_welcome_text) self.window().export_channel_button.setHidden(False) self.window().edit_channel_name_label.setText("My channel") self.window().edit_channel_overview_name_label.setText(self.channel_overview["name"]) self.window().edit_channel_description_label.setText(self.channel_overview["description"]) self.window().edit_channel_identifier_label.setText(self.channel_overview["identifier"]) self.window().edit_channel_name_edit.setText(self.channel_overview["name"]) self.window().edit_channel_description_edit.setText(self.channel_overview["description"]) self.window().edit_channel_stacked_widget.setCurrentIndex(1) def load_channel_torrents(self): self.window().edit_channel_torrents_list.set_data_items([(LoadingListItem, None)]) self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request("channels/discovered/%s/torrents?disable_filter=1" % self.channel_overview["identifier"], self.initialize_with_torrents) def initialize_with_torrents(self, torrents): if not torrents: return self.window().edit_channel_torrents_list.set_data_items([]) self.window().dirty_channel_widget.setHidden(not("chant_dirty" in torrents and torrents["chant_dirty"])) items = [] for result in torrents['torrents']: items.append((ChannelTorrentListItem, result, {"show_controls": True, "on_remove_clicked": self.on_torrent_remove_clicked})) self.window().edit_channel_torrents_list.set_data_items(items) def load_channel_playlists(self): self.window().edit_channel_playlists_list.set_data_items([(LoadingListItem, None)]) self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request("channels/discovered/%s/playlists?disable_filter=1" % self.channel_overview["identifier"], self.initialize_with_playlists) def initialize_with_playlists(self, playlists): if not playlists: return self.playlists_loaded.emit(playlists) self.playlists = playlists self.window().edit_channel_playlists_list.set_data_items([]) self.update_playlist_list() viewing_playlist_index = self.get_index_of_viewing_playlist() if viewing_playlist_index != -1: self.viewing_playlist = self.playlists['playlists'][viewing_playlist_index] self.update_playlist_torrent_list() def load_channel_rss_feeds(self): self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request("channels/discovered/%s/rssfeeds" % self.channel_overview["identifier"], self.initialize_with_rss_feeds) def initialize_with_rss_feeds(self, rss_feeds): if not rss_feeds: return self.window().edit_channel_rss_feeds_list.clear() for feed in rss_feeds["rssfeeds"]: item = QTreeWidgetItem(self.window().edit_channel_rss_feeds_list) item.setText(0, feed["url"]) self.window().edit_channel_rss_feeds_list.addTopLevelItem(item) def on_torrent_remove_clicked(self, item): if "chant" in self.channel_overview: self.on_torrents_remove_selected_action(0, item) return self.dialog = ConfirmationDialog(self, "Remove selected torrent", "Are you sure that you want to remove the selected torrent from this channel?", [('CONFIRM', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)]) self.dialog.button_clicked.connect(lambda action: self.on_torrents_remove_selected_action(action, item)) self.dialog.show() def on_create_channel_button_pressed(self): channel_name = self.window().new_channel_name_edit.text() channel_description = self.window().new_channel_description_edit.toPlainText() if len(channel_name) == 0: self.window().new_channel_name_label.setStyleSheet("color: red;") return self.window().create_channel_button.setEnabled(False) self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request("channels/discovered", self.on_channel_created, data=unicode('name=%s&description=%s' % (channel_name, channel_description)).encode('utf-8'), method='PUT') def on_channel_created(self, result): if not result: return if u'added' in result: self.window().create_channel_button.setEnabled(True) self.load_my_channel_overview() def on_edit_channel_save_button_pressed(self): channel_name = self.window().edit_channel_name_edit.text() channel_description = self.window().edit_channel_description_edit.toPlainText() self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request("mychannel", self.on_channel_edited, data=unicode('name=%s&description=%s' % (channel_name, channel_description)).encode('utf-8'), method='POST') def clicked_edit_channel_commit_button(self): self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request("mychannel", self.on_channel_committed, data=unicode('commit_changes=1').encode('utf-8'), method='POST') def on_channel_committed(self, result): if not result: return if 'modified' in result: self.load_channel_torrents() def on_channel_edited(self, result): if not result: return if 'modified' in result: self.window().edit_channel_name_label.setText(self.window().edit_channel_name_edit.text()) self.window().edit_channel_description_label.setText( self.window().edit_channel_description_edit.toPlainText()) def on_torrents_remove_selected_clicked(self): num_selected = len(self.window().edit_channel_torrents_list.selectedItems()) if num_selected == 0: return selected_torrent_items = [self.window().edit_channel_torrents_list.itemWidget(list_widget_item) for list_widget_item in self.window().edit_channel_torrents_list.selectedItems()] self.dialog = ConfirmationDialog(self, "Remove %s selected torrents" % num_selected, "Are you sure that you want to remove %s selected torrents " "from your channel?" % num_selected, [('CONFIRM', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)]) self.dialog.button_clicked.connect(lambda action: self.on_torrents_remove_selected_action(action, selected_torrent_items)) self.dialog.show() def on_torrents_remove_all_clicked(self): self.dialog = ConfirmationDialog(self.window(), "Remove all torrents", "Are you sure that you want to remove all torrents from your channel? " "You cannot undo this action.", [('CONFIRM', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)]) self.dialog.button_clicked.connect(self.on_torrents_remove_all_action) self.dialog.show() def on_torrents_add_clicked(self): menu = TriblerActionMenu(self) browse_files_action = QAction('Import torrent from file', self) browse_dir_action = QAction('Import torrent(s) from dir', self) add_url_action = QAction('Add URL', self) create_torrent_action = QAction('Create torrent from file(s)', self) browse_files_action.triggered.connect(self.on_add_torrent_browse_file) browse_dir_action.triggered.connect(self.on_add_torrents_browse_dir) add_url_action.triggered.connect(self.on_add_torrent_from_url) create_torrent_action.triggered.connect(self.on_create_torrent_from_files) menu.addAction(browse_files_action) menu.addAction(browse_dir_action) menu.addAction(add_url_action) menu.addAction(create_torrent_action) menu.exec_(QCursor.pos()) def add_torrent_to_channel(self, filename): with open(filename, "rb") as torrent_file: torrent_content = urllib.quote_plus(base64.b64encode(torrent_file.read())) editchannel_request_mgr = TriblerRequestManager() editchannel_request_mgr.perform_request("channels/discovered/%s/torrents" % self.channel_overview['identifier'], self.on_torrent_to_channel_added, method='PUT', data='torrent=%s' % torrent_content) def on_add_torrent_browse_file(self): filename = QFileDialog.getOpenFileName(self, "Please select the .torrent file", "", "Torrent files (*.torrent)") if len(filename[0]) == 0: return self.add_torrent_to_channel(filename[0]) def on_add_torrent_from_url(self): self.dialog = ConfirmationDialog(self, "Add torrent from URL/magnet link", "Please enter the URL/magnet link in the field below:", [('ADD', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)], show_input=True) self.dialog.dialog_widget.dialog_input.setPlaceholderText('URL/magnet link') self.dialog.button_clicked.connect(self.on_torrent_from_url_dialog_done) self.dialog.show() def on_torrent_from_url_dialog_done(self, action): if action == 0: url = urllib.quote_plus(self.dialog.dialog_widget.dialog_input.text()) self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request("channels/discovered/%s/torrents/%s" % (self.channel_overview['identifier'], url), self.on_torrent_to_channel_added, method='PUT') self.dialog.close_dialog() self.dialog = None def on_torrent_to_channel_added(self, result): if not result: return if 'added' in result: self.load_channel_torrents() def on_create_torrent_from_files(self): self.window().edit_channel_details_create_torrent.initialize(self.channel_overview['identifier']) self.window().edit_channel_details_stacked_widget.setCurrentIndex(PAGE_EDIT_CHANNEL_CREATE_TORRENT) def on_playlist_torrents_back_clicked(self): self.window().edit_channel_details_stacked_widget.setCurrentIndex(PAGE_EDIT_CHANNEL_PLAYLISTS) def on_playlist_item_clicked(self, item): playlist_info = item.data(Qt.UserRole) if not playlist_info: return self.window().edit_channel_playlist_torrents_list.set_data_items([]) self.window().edit_channel_details_playlist_torrents_header.setText("Torrents in '%s'" % playlist_info['name']) self.window().edit_channel_playlist_torrents_back.setIcon(QIcon(get_image_path('page_back.png'))) self.viewing_playlist = playlist_info self.update_playlist_torrent_list() self.window().edit_channel_details_stacked_widget.setCurrentIndex(PAGE_EDIT_CHANNEL_PLAYLIST_TORRENTS) def update_playlist_list(self): self.playlists['playlists'].sort(key=lambda torrent: len(torrent['torrents']), reverse=True) items = [] for result in self.playlists['playlists']: items.append((PlaylistListItem, result, {"show_controls": True, "on_remove_clicked": self.on_playlist_remove_clicked, "on_edit_clicked": self.on_playlist_edit_clicked})) self.window().edit_channel_playlists_list.set_data_items(items) def update_playlist_torrent_list(self): items = [] for torrent in self.viewing_playlist["torrents"]: items.append((ChannelTorrentListItem, torrent, {"show_controls": True, "on_remove_clicked": self.on_playlist_torrent_remove_clicked})) self.window().edit_channel_playlist_torrents_list.set_data_items(items) def on_playlist_manage_clicked(self): self.window().edit_channel_details_playlist_manage.initialize(self.channel_overview, self.viewing_playlist) self.window().edit_channel_details_stacked_widget.setCurrentIndex(PAGE_EDIT_CHANNEL_PLAYLIST_MANAGE) def on_playlist_torrent_remove_clicked(self, item): self.dialog = ConfirmationDialog(self, "Remove selected torrent from playlist", "Are you sure that you want to remove the selected torrent " "from this playlist?", [('CONFIRM', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)]) self.dialog.button_clicked.connect(lambda action: self.on_playlist_torrent_remove_selected_action(item, action)) self.dialog.show() def on_playlist_torrent_remove_selected_action(self, item, action): if action == 0: self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request("channels/discovered/%s/playlists/%s/%s" % (self.channel_overview["identifier"], self.viewing_playlist['id'], item.torrent_info['infohash']), lambda result: self.on_playlist_torrent_removed( result, item.torrent_info), method='DELETE') self.dialog.close_dialog() self.dialog = None def on_playlist_torrent_removed(self, result, torrent): if not result: return self.remove_torrent_from_playlist(torrent) def get_index_of_viewing_playlist(self): if self.viewing_playlist is None: return -1 for index in xrange(len(self.playlists['playlists'])): if self.playlists['playlists'][index]['id'] == self.viewing_playlist['id']: return index return -1 def remove_torrent_from_playlist(self, torrent): playlist_index = self.get_index_of_viewing_playlist() torrent_index = -1 for index in xrange(len(self.viewing_playlist['torrents'])): if self.viewing_playlist['torrents'][index]['infohash'] == torrent['infohash']: torrent_index = index break if torrent_index != -1: del self.playlists['playlists'][playlist_index]['torrents'][torrent_index] self.viewing_playlist = self.playlists['playlists'][playlist_index] self.update_playlist_list() self.update_playlist_torrent_list() def on_playlist_edit_save_clicked(self): if len(self.window().playlist_edit_name.text()) == 0: return name = self.window().playlist_edit_name.text() description = self.window().playlist_edit_description.toPlainText() self.editchannel_request_mgr = TriblerRequestManager() if self.editing_playlist is None: self.editchannel_request_mgr.perform_request("channels/discovered/%s/playlists" % self.channel_overview["identifier"], self.on_playlist_created, data=unicode('name=%s&description=%s' % (name, description)).encode('utf-8'), method='PUT') else: self.editchannel_request_mgr.perform_request("channels/discovered/%s/playlists/%s" % (self.channel_overview["identifier"], self.editing_playlist["id"]), self.on_playlist_edited, data=unicode('name=%s&description=%s' % (name, description)).encode('utf-8'), method='POST') def on_playlist_created(self, json_result): if not json_result: return if 'created' in json_result and json_result['created']: self.on_playlist_edited_done() def on_playlist_edited(self, json_result): if not json_result: return if 'modified' in json_result and json_result['modified']: self.on_playlist_edited_done() def on_playlist_edited_done(self): self.window().playlist_edit_name.setText('') self.window().playlist_edit_description.setText('') self.load_channel_playlists() self.window().edit_channel_details_stacked_widget.setCurrentIndex(PAGE_EDIT_CHANNEL_PLAYLISTS) def on_playlist_edit_cancel_clicked(self): self.window().edit_channel_details_stacked_widget.setCurrentIndex(PAGE_EDIT_CHANNEL_PLAYLISTS) def on_playlist_created_clicked(self): self.editing_playlist = None self.window().playlist_edit_save_button.setText("CREATE") self.window().edit_channel_details_stacked_widget.setCurrentIndex(PAGE_EDIT_CHANNEL_PLAYLIST_EDIT) def on_playlist_remove_clicked(self, item): self.dialog = ConfirmationDialog(self, "Remove selected playlist", "Are you sure that you want to remove the selected playlist " "from your channel?", [('CONFIRM', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)]) self.dialog.button_clicked.connect(lambda action: self.on_playlist_remove_selected_action(item, action)) self.dialog.show() def on_playlist_remove_selected_action(self, item, action): if action == 0: self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request("channels/discovered/%s/playlists/%s" % (self.channel_overview["identifier"], item.playlist_info['id']), self.on_playlist_removed, method='DELETE') self.dialog.close_dialog() self.dialog = None def on_playlist_removed(self, json_result): if not json_result: return if 'removed' in json_result and json_result['removed']: self.load_channel_playlists() def on_playlist_edit_clicked(self, item): self.editing_playlist = item.playlist_info self.window().playlist_edit_save_button.setText("CREATE") self.window().playlist_edit_name.setText(item.playlist_info["name"]) self.window().playlist_edit_description.setText(item.playlist_info["description"]) self.window().edit_channel_details_stacked_widget.setCurrentIndex(PAGE_EDIT_CHANNEL_PLAYLIST_EDIT) def on_torrents_remove_selected_action(self, action, items): if action == 0: if isinstance(items, list): infohash = ",".join([torrent_item.torrent_info['infohash'] for torrent_item in items]) else: infohash = items.torrent_info['infohash'] self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request("channels/discovered/%s/torrents/%s" % (self.channel_overview["identifier"], infohash), self.on_torrent_removed, method='DELETE') if self.dialog: self.dialog.close_dialog() self.dialog = None def on_torrent_removed(self, json_result): if not json_result: return if 'removed' in json_result and json_result['removed']: self.load_channel_torrents() def on_torrents_remove_all_action(self, action): if action == 0: for torrent_ind in xrange(self.window().edit_channel_torrents_list.count()): torrent_data = self.window().edit_channel_torrents_list.item(torrent_ind).data(Qt.UserRole) request_mgr = TriblerRequestManager() request_mgr.perform_request("channels/discovered/%s/torrents/%s" % (self.channel_overview["identifier"], torrent_data['infohash']), None, method='DELETE') self.remove_torrent_requests.append(request_mgr) self.window().edit_channel_torrents_list.set_data_items([]) if "chant" in self.channel_overview: self.load_channel_torrents() self.dialog.close_dialog() self.dialog = None def clicked_tab_button(self, tab_button_name): if tab_button_name == "edit_channel_overview_button": self.window().edit_channel_details_stacked_widget.setCurrentIndex(PAGE_EDIT_CHANNEL_OVERVIEW) elif tab_button_name == "edit_channel_settings_button": self.window().edit_channel_details_stacked_widget.setCurrentIndex(PAGE_EDIT_CHANNEL_SETTINGS) elif tab_button_name == "edit_channel_torrents_button": self.window().edit_channel_details_stacked_widget.setCurrentIndex(PAGE_EDIT_CHANNEL_TORRENTS) self.load_channel_torrents() elif tab_button_name == "edit_channel_playlists_button": self.window().edit_channel_details_stacked_widget.setCurrentIndex(PAGE_EDIT_CHANNEL_PLAYLISTS) self.load_channel_playlists() elif tab_button_name == "edit_channel_rss_feeds_button": self.window().edit_channel_details_stacked_widget.setCurrentIndex(PAGE_EDIT_CHANNEL_RSS_FEEDS) self.load_channel_rss_feeds() def on_create_channel_intro_button_clicked(self): self.window().create_channel_form.show() self.window().create_channel_intro_button_container.hide() self.window().create_new_channel_intro_label.setText("Please enter your channel details below.") def on_rss_feed_add_clicked(self): self.dialog = ConfirmationDialog(self, "Add RSS feed", "Please enter the RSS feed URL in the field below:", [('ADD', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)], show_input=True) self.dialog.dialog_widget.dialog_input.setPlaceholderText('RSS feed URL') self.dialog.button_clicked.connect(self.on_rss_feed_dialog_added) self.dialog.show() def on_rss_feed_dialog_added(self, action): if action == 0: url = urllib.quote_plus(self.dialog.dialog_widget.dialog_input.text()) self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request("channels/discovered/%s/rssfeeds/%s" % (self.channel_overview["identifier"], url), self.on_rss_feed_added, method='PUT') self.dialog.close_dialog() self.dialog = None def on_rss_feed_added(self, json_result): if not json_result: return if json_result['added']: self.load_channel_rss_feeds() def on_rss_feeds_remove_selected_clicked(self): if len(self.window().edit_channel_rss_feeds_list.selectedItems()) == 0: ConfirmationDialog.show_message(self, "Remove RSS Feeds", "Selection is empty. Please select the feeds to remove.", "OK") return self.dialog = ConfirmationDialog(self, "Remove RSS feed", "Are you sure you want to remove the selected RSS feed?", [('REMOVE', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)]) self.dialog.button_clicked.connect(self.on_rss_feed_dialog_removed) self.dialog.show() def on_rss_feed_dialog_removed(self, action): if action == 0: url = urllib.quote_plus(self.window().edit_channel_rss_feeds_list.selectedItems()[0].text(0)) self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request("channels/discovered/%s/rssfeeds/%s" % (self.channel_overview["identifier"], url), self.on_rss_feed_removed, method='DELETE') self.dialog.close_dialog() self.dialog = None def on_rss_feed_removed(self, json_result): if not json_result: return if json_result['removed']: self.load_channel_rss_feeds() def on_rss_feeds_refresh_clicked(self): self.window().edit_channel_details_rss_refresh_button.setEnabled(False) self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request('channels/discovered/%s/recheckfeeds' % self.channel_overview["identifier"], self.on_rss_feeds_refreshed,\ method='POST') def on_rss_feeds_refreshed(self, json_result): if not json_result: return if json_result["rechecked"]: self.window().edit_channel_details_rss_refresh_button.setEnabled(True) def on_export_mdblob(self): export_dir = QFileDialog.getExistingDirectory(self, "Please select the destination directory", "", QFileDialog.ShowDirsOnly) if len(export_dir) == 0: return # Show confirmation dialog where we specify the name of the file mdblob_name = self.channel_overview["identifier"] dialog = ConfirmationDialog(self, "Export mdblob file", "Please enter the name of the channel metadata file:", [('SAVE', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)], show_input=True) 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_request_done(dest_path, data): 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) dialog.dialog_widget.dialog_input.setPlaceholderText('Channel file name') dialog.dialog_widget.dialog_input.setText("%s.mdblob" % mdblob_name) dialog.dialog_widget.dialog_input.setFocus() dialog.button_clicked.connect(on_export_download_dialog_done) dialog.show() def on_add_torrents_browse_dir(self): chosen_dir = QFileDialog.getExistingDirectory(self, "Please select the directory containing the .torrent files", QDir.homePath(), QFileDialog.ShowDirsOnly) if len(chosen_dir) == 0: return self.selected_torrent_files = [torrent_file for torrent_file in glob.glob(chosen_dir + "/*.torrent")] self.dialog = ConfirmationDialog(self, "Add torrents from directory", "Are you sure you want to add %d torrents to your Tribler channel?" % len(self.selected_torrent_files), [('ADD', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)]) self.dialog.button_clicked.connect(self.on_confirm_add_directory_dialog) self.dialog.show() def on_confirm_add_directory_dialog(self, action): if action == 0: for filename in self.selected_torrent_files: self.add_torrent_to_channel(filename) if self.dialog: self.dialog.close_dialog() self.dialog = None
class EditChannelPage(QWidget): """ This class is responsible for managing lists and data on the your channel page, including torrents, playlists and rss feeds. """ playlists_loaded = pyqtSignal(object) def __init__(self): QWidget.__init__(self) self.remove_torrent_requests = [] self.channel_overview = None self.playlists = None self.editing_playlist = None self.viewing_playlist = None self.editing_own_channel = False self.dialog = None self.editchannel_request_mgr = None def initialize_edit_channel_page(self): self.window().create_channel_intro_button.clicked.connect( self.on_create_channel_intro_button_clicked) self.window().create_channel_form.hide() self.window().edit_channel_stacked_widget.setCurrentIndex(1) self.window().edit_channel_details_stacked_widget.setCurrentIndex( PAGE_EDIT_CHANNEL_OVERVIEW) self.window().create_channel_button.clicked.connect( self.on_create_channel_button_pressed) self.window().edit_channel_save_button.clicked.connect( self.on_edit_channel_save_button_pressed) self.window( ).edit_channel_torrents_remove_selected_button.clicked.connect( self.on_torrents_remove_selected_clicked) self.window().edit_channel_torrents_remove_all_button.clicked.connect( self.on_torrents_remove_all_clicked) self.window().edit_channel_torrents_add_button.clicked.connect( self.on_torrents_add_clicked) self.window( ).edit_channel_details_playlist_manage.playlist_saved.connect( self.load_channel_playlists) self.window().edit_channel_playlist_torrents_back.clicked.connect( self.on_playlist_torrents_back_clicked) self.window().edit_channel_playlists_list.itemClicked.connect( self.on_playlist_item_clicked) self.window( ).edit_channel_playlist_manage_torrents_button.clicked.connect( self.on_playlist_manage_clicked) self.window().edit_channel_create_playlist_button.clicked.connect( self.on_playlist_created_clicked) self.window().playlist_edit_save_button.clicked.connect( self.on_playlist_edit_save_clicked) self.window().playlist_edit_cancel_button.clicked.connect( self.on_playlist_edit_cancel_clicked) self.window( ).edit_channel_details_rss_feeds_remove_selected_button.clicked.connect( self.on_rss_feeds_remove_selected_clicked) self.window().edit_channel_details_rss_add_button.clicked.connect( self.on_rss_feed_add_clicked) self.window().edit_channel_details_rss_refresh_button.clicked.connect( self.on_rss_feeds_refresh_clicked) # Tab bar buttons self.window().channel_settings_tab.initialize() self.window().channel_settings_tab.clicked_tab_button.connect( self.clicked_tab_button) def load_my_channel_overview(self): if not self.channel_overview: self.window().edit_channel_stacked_widget.setCurrentIndex(2) self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request( "mychannel", self.initialize_with_channel_overview, capture_errors=False) def initialize_with_channel_overview(self, overview): if 'error' in overview: self.window().edit_channel_stacked_widget.setCurrentIndex(0) else: if "mychannel" in overview: self.channel_overview = overview["mychannel"] self.set_editing_own_channel(True) self.window().edit_channel_name_label.setText("My channel") else: self.channel_overview = overview["channel"] self.set_editing_own_channel(False) self.window().edit_channel_name_label.setText( self.channel_overview["name"]) self.window().edit_channel_overview_name_label.setText( self.channel_overview["name"]) self.window().edit_channel_description_label.setText( self.channel_overview["description"]) self.window().edit_channel_identifier_label.setText( self.channel_overview["identifier"]) self.window().edit_channel_name_edit.setText( self.channel_overview["name"]) self.window().edit_channel_description_edit.setText( self.channel_overview["description"]) self.window().edit_channel_stacked_widget.setCurrentIndex(1) def set_editing_own_channel(self, edit_own): self.editing_own_channel = edit_own self.window().edit_channel_settings_button.setHidden(not edit_own) self.window().edit_channel_rss_feeds_button.setHidden(not edit_own) self.window().edit_channel_playlists_button.setHidden(not edit_own) self.window().edit_channel_torrents_remove_all_button.setHidden( not edit_own) self.window().edit_channel_torrents_remove_selected_button.setHidden( not edit_own) def load_channel_torrents(self): self.window().edit_channel_torrents_list.set_data_items([ (LoadingListItem, None) ]) self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request( "channels/discovered/%s/torrents?disable_filter=1" % self.channel_overview["identifier"], self.initialize_with_torrents) def initialize_with_torrents(self, torrents): self.window().edit_channel_torrents_list.set_data_items([]) items = [] for result in torrents['torrents']: items.append((ChannelTorrentListItem, result, { "show_controls": True, "on_remove_clicked": self.on_torrent_remove_clicked })) self.window().edit_channel_torrents_list.set_data_items(items) def load_channel_playlists(self): self.window().edit_channel_playlists_list.set_data_items([ (LoadingListItem, None) ]) self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request( "channels/discovered/%s/playlists?disable_filter=1" % self.channel_overview["identifier"], self.initialize_with_playlists) def initialize_with_playlists(self, playlists): self.playlists_loaded.emit(playlists) self.playlists = playlists self.window().edit_channel_playlists_list.set_data_items([]) self.update_playlist_list() viewing_playlist_index = self.get_index_of_viewing_playlist() if viewing_playlist_index != -1: self.viewing_playlist = self.playlists['playlists'][ viewing_playlist_index] self.update_playlist_torrent_list() def load_channel_rss_feeds(self): self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request( "channels/discovered/%s/rssfeeds" % self.channel_overview["identifier"], self.initialize_with_rss_feeds) def initialize_with_rss_feeds(self, rss_feeds): self.window().edit_channel_rss_feeds_list.clear() for feed in rss_feeds["rssfeeds"]: item = QTreeWidgetItem(self.window().edit_channel_rss_feeds_list) item.setText(0, feed["url"]) self.window().edit_channel_rss_feeds_list.addTopLevelItem(item) def on_torrent_remove_clicked(self, item): self.dialog = ConfirmationDialog( self, "Remove selected torrent", "Are you sure that you want to remove the selected torrent from this channel?", [('CONFIRM', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)]) self.dialog.button_clicked.connect( lambda action: self.on_torrents_remove_selected_action( action, item)) self.dialog.show() def on_create_channel_button_pressed(self): channel_name = self.window().new_channel_name_edit.text() channel_description = self.window( ).new_channel_description_edit.toPlainText() if len(channel_name) == 0: self.window().new_channel_name_label.setStyleSheet("color: red;") return self.window().create_channel_button.setEnabled(False) self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request( "channels/discovered", self.on_channel_created, data=unicode('name=%s&description=%s' % (channel_name, channel_description)).encode('utf-8'), method='PUT') def on_channel_created(self, result): if u'added' in result: self.window().create_channel_button.setEnabled(True) self.load_my_channel_overview() def on_edit_channel_save_button_pressed(self): channel_name = self.window().edit_channel_name_edit.text() channel_description = self.window( ).edit_channel_description_edit.toPlainText() self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request( "mychannel", self.on_channel_edited, data=unicode('name=%s&description=%s' % (channel_name, channel_description)).encode('utf-8'), method='POST') def on_channel_edited(self, result): if 'modified' in result: self.window().edit_channel_name_label.setText( self.window().edit_channel_name_edit.text()) self.window().edit_channel_description_label.setText( self.window().edit_channel_description_edit.toPlainText()) def on_torrents_remove_selected_clicked(self): num_selected = len( self.window().edit_channel_torrents_list.selectedItems()) if num_selected == 0: return selected_torrent_items = [ self.window().edit_channel_torrents_list.itemWidget( list_widget_item) for list_widget_item in self.window().edit_channel_torrents_list.selectedItems() ] self.dialog = ConfirmationDialog( self, "Remove %s selected torrents" % num_selected, "Are you sure that you want to remove %s selected torrents " "from your channel?" % num_selected, [('CONFIRM', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)]) self.dialog.button_clicked.connect( lambda action: self.on_torrents_remove_selected_action( action, selected_torrent_items)) self.dialog.show() def on_torrents_remove_all_clicked(self): self.dialog = ConfirmationDialog( self.window(), "Remove all torrents", "Are you sure that you want to remove all torrents from your channel? " "You cannot undo this action.", [('CONFIRM', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)]) self.dialog.button_clicked.connect(self.on_torrents_remove_all_action) self.dialog.show() def on_torrents_add_clicked(self): menu = TriblerActionMenu(self) browse_files_action = QAction('Import torrent from file', self) add_url_action = QAction('Add URL', self) create_torrent_action = QAction('Create torrent from file(s)', self) browse_files_action.triggered.connect(self.on_add_torrent_browse_file) add_url_action.triggered.connect(self.on_add_torrent_from_url) create_torrent_action.triggered.connect( self.on_create_torrent_from_files) menu.addAction(browse_files_action) menu.addAction(add_url_action) menu.addAction(create_torrent_action) menu.exec_(QCursor.pos()) def on_add_torrent_browse_file(self): filename = QFileDialog.getOpenFileName( self, "Please select the .torrent file", "", "Torrent files (*.torrent)") if len(filename[0]) == 0: return with open(filename[0], "rb") as torrent_file: torrent_content = urllib.quote_plus( base64.b64encode(torrent_file.read())) self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request( "channels/discovered/%s/torrents" % self.channel_overview['identifier'], self.on_torrent_to_channel_added, method='PUT', data='torrent=%s' % torrent_content) def on_add_torrent_from_url(self): self.dialog = ConfirmationDialog( self, "Add torrent from URL/magnet link", "Please enter the URL/magnet link in the field below:", [('ADD', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)], show_input=True) self.dialog.dialog_widget.dialog_input.setPlaceholderText( 'URL/magnet link') self.dialog.button_clicked.connect( self.on_torrent_from_url_dialog_done) self.dialog.show() def on_torrent_from_url_dialog_done(self, action): if action == 0: url = urllib.quote_plus( self.dialog.dialog_widget.dialog_input.text()) self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request( "channels/discovered/%s/torrents/%s" % (self.channel_overview['identifier'], url), self.on_torrent_to_channel_added, method='PUT') self.dialog.setParent(None) self.dialog = None def on_torrent_to_channel_added(self, result): if 'added' in result: self.load_channel_torrents() def on_create_torrent_from_files(self): self.window().edit_channel_details_create_torrent.initialize( self.channel_overview['identifier']) self.window().edit_channel_details_stacked_widget.setCurrentIndex( PAGE_EDIT_CHANNEL_CREATE_TORRENT) def on_playlist_torrents_back_clicked(self): self.window().edit_channel_details_stacked_widget.setCurrentIndex( PAGE_EDIT_CHANNEL_PLAYLISTS) def on_playlist_item_clicked(self, item): playlist_info = item.data(Qt.UserRole) self.window().edit_channel_playlist_torrents_list.set_data_items([]) self.window().edit_channel_details_playlist_torrents_header.setText( "Torrents in '%s'" % playlist_info['name']) self.window().edit_channel_playlist_torrents_back.setIcon( QIcon(get_image_path('page_back.png'))) self.viewing_playlist = playlist_info self.update_playlist_torrent_list() self.window().edit_channel_details_stacked_widget.setCurrentIndex( PAGE_EDIT_CHANNEL_PLAYLIST_TORRENTS) def update_playlist_list(self): self.playlists['playlists'].sort( key=lambda torrent: len(torrent['torrents']), reverse=True) items = [] for result in self.playlists['playlists']: items.append((PlaylistListItem, result, { "show_controls": True, "on_remove_clicked": self.on_playlist_remove_clicked, "on_edit_clicked": self.on_playlist_edit_clicked })) self.window().edit_channel_playlists_list.set_data_items(items) def update_playlist_torrent_list(self): items = [] for torrent in self.viewing_playlist["torrents"]: items.append((ChannelTorrentListItem, torrent, { "show_controls": True, "on_remove_clicked": self.on_playlist_torrent_remove_clicked })) self.window().edit_channel_playlist_torrents_list.set_data_items(items) def on_playlist_manage_clicked(self): self.window().edit_channel_details_playlist_manage.initialize( self.channel_overview, self.viewing_playlist) self.window().edit_channel_details_stacked_widget.setCurrentIndex( PAGE_EDIT_CHANNEL_PLAYLIST_MANAGE) def on_playlist_torrent_remove_clicked(self, item): self.dialog = ConfirmationDialog( self, "Remove selected torrent from playlist", "Are you sure that you want to remove the selected torrent " "from this playlist?", [('CONFIRM', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)]) self.dialog.button_clicked.connect( lambda action: self.on_playlist_torrent_remove_selected_action( item, action)) self.dialog.show() def on_playlist_torrent_remove_selected_action(self, item, action): if action == 0: self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request( "channels/discovered/%s/playlists/%s/%s" % (self.channel_overview["identifier"], self.viewing_playlist['id'], item.torrent_info['infohash']), lambda result: self.on_playlist_torrent_removed( result, item.torrent_info), method='DELETE') self.dialog.setParent(None) self.dialog = None def on_playlist_torrent_removed(self, result, torrent): self.remove_torrent_from_playlist(torrent) def get_index_of_viewing_playlist(self): if self.viewing_playlist is None: return -1 for index in xrange(len(self.playlists['playlists'])): if self.playlists['playlists'][index][ 'id'] == self.viewing_playlist['id']: return index return -1 def remove_torrent_from_playlist(self, torrent): playlist_index = self.get_index_of_viewing_playlist() torrent_index = -1 for index in xrange(len(self.viewing_playlist['torrents'])): if self.viewing_playlist['torrents'][index]['infohash'] == torrent[ 'infohash']: torrent_index = index break if torrent_index != -1: del self.playlists['playlists'][playlist_index]['torrents'][ torrent_index] self.viewing_playlist = self.playlists['playlists'][playlist_index] self.update_playlist_list() self.update_playlist_torrent_list() def on_playlist_edit_save_clicked(self): if len(self.window().playlist_edit_name.text()) == 0: return name = self.window().playlist_edit_name.text() description = self.window().playlist_edit_description.toPlainText() self.editchannel_request_mgr = TriblerRequestManager() if self.editing_playlist is None: self.editchannel_request_mgr.perform_request( "channels/discovered/%s/playlists" % self.channel_overview["identifier"], self.on_playlist_created, data=unicode('name=%s&description=%s' % (name, description)).encode('utf-8'), method='PUT') else: self.editchannel_request_mgr.perform_request( "channels/discovered/%s/playlists/%s" % (self.channel_overview["identifier"], self.editing_playlist["id"]), self.on_playlist_edited, data=unicode('name=%s&description=%s' % (name, description)).encode('utf-8'), method='POST') def on_playlist_created(self, json_result): if 'created' in json_result and json_result['created']: self.on_playlist_edited_done() def on_playlist_edited(self, json_result): if 'modified' in json_result and json_result['modified']: self.on_playlist_edited_done() def on_playlist_edited_done(self): self.window().playlist_edit_name.setText('') self.window().playlist_edit_description.setText('') self.load_channel_playlists() self.window().edit_channel_details_stacked_widget.setCurrentIndex( PAGE_EDIT_CHANNEL_PLAYLISTS) def on_playlist_edit_cancel_clicked(self): self.window().edit_channel_details_stacked_widget.setCurrentIndex( PAGE_EDIT_CHANNEL_PLAYLISTS) def on_playlist_created_clicked(self): self.editing_playlist = None self.window().playlist_edit_save_button.setText("CREATE") self.window().edit_channel_details_stacked_widget.setCurrentIndex( PAGE_EDIT_CHANNEL_PLAYLIST_EDIT) def on_playlist_remove_clicked(self, item): self.dialog = ConfirmationDialog( self, "Remove selected playlist", "Are you sure that you want to remove the selected playlist " "from your channel?", [('CONFIRM', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)]) self.dialog.button_clicked.connect( lambda action: self.on_playlist_remove_selected_action( item, action)) self.dialog.show() def on_playlist_remove_selected_action(self, item, action): if action == 0: self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request( "channels/discovered/%s/playlists/%s" % (self.channel_overview["identifier"], item.playlist_info['id']), self.on_playlist_removed, method='DELETE') self.dialog.setParent(None) self.dialog = None def on_playlist_removed(self, json_result): if 'removed' in json_result and json_result['removed']: self.load_channel_playlists() def on_playlist_edit_clicked(self, item): self.editing_playlist = item.playlist_info self.window().playlist_edit_save_button.setText("CREATE") self.window().playlist_edit_name.setText(item.playlist_info["name"]) self.window().playlist_edit_description.setText( item.playlist_info["description"]) self.window().edit_channel_details_stacked_widget.setCurrentIndex( PAGE_EDIT_CHANNEL_PLAYLIST_EDIT) def on_torrents_remove_selected_action(self, action, items): if action == 0: if isinstance(items, list): infohash = ",".join([ torrent_item.torrent_info['infohash'] for torrent_item in items ]) else: infohash = items.torrent_info['infohash'] self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request( "channels/discovered/%s/torrents/%s" % (self.channel_overview["identifier"], infohash), self.on_torrent_removed, method='DELETE') self.dialog.setParent(None) self.dialog = None def on_torrent_removed(self, json_result): if 'removed' in json_result and json_result['removed']: self.load_channel_torrents() def on_torrents_remove_all_action(self, action): if action == 0: for torrent_ind in xrange( self.window().edit_channel_torrents_list.count()): torrent_data = self.window().edit_channel_torrents_list.item( torrent_ind).data(Qt.UserRole) request_mgr = TriblerRequestManager() request_mgr.perform_request( "channels/discovered/%s/torrents/%s" % (self.channel_overview["identifier"], torrent_data['infohash']), None, method='DELETE') self.remove_torrent_requests.append(request_mgr) self.window().edit_channel_torrents_list.set_data_items([]) self.dialog.setParent(None) self.dialog = None def clicked_tab_button(self, tab_button_name): if tab_button_name == "edit_channel_overview_button": self.window().edit_channel_details_stacked_widget.setCurrentIndex( PAGE_EDIT_CHANNEL_OVERVIEW) elif tab_button_name == "edit_channel_settings_button": self.window().edit_channel_details_stacked_widget.setCurrentIndex( PAGE_EDIT_CHANNEL_SETTINGS) elif tab_button_name == "edit_channel_torrents_button": self.window().edit_channel_details_stacked_widget.setCurrentIndex( PAGE_EDIT_CHANNEL_TORRENTS) self.load_channel_torrents() elif tab_button_name == "edit_channel_playlists_button": self.window().edit_channel_details_stacked_widget.setCurrentIndex( PAGE_EDIT_CHANNEL_PLAYLISTS) self.load_channel_playlists() elif tab_button_name == "edit_channel_rss_feeds_button": self.window().edit_channel_details_stacked_widget.setCurrentIndex( PAGE_EDIT_CHANNEL_RSS_FEEDS) self.load_channel_rss_feeds() def on_create_channel_intro_button_clicked(self): self.window().create_channel_form.show() self.window().create_channel_intro_button_container.hide() self.window().create_new_channel_intro_label.setText( "Please enter your channel details below.") def on_rss_feed_add_clicked(self): self.dialog = ConfirmationDialog( self, "Add RSS feed", "Please enter the RSS feed URL in the field below:", [('ADD', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)], show_input=True) self.dialog.dialog_widget.dialog_input.setPlaceholderText( 'RSS feed URL') self.dialog.button_clicked.connect(self.on_rss_feed_dialog_added) self.dialog.show() def on_rss_feed_dialog_added(self, action): if action == 0: url = urllib.quote_plus( self.dialog.dialog_widget.dialog_input.text()) self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request( "channels/discovered/%s/rssfeeds/%s" % (self.channel_overview["identifier"], url), self.on_rss_feed_added, method='PUT') self.dialog.setParent(None) self.dialog = None def on_rss_feed_added(self, json_result): if json_result['added']: self.load_channel_rss_feeds() def on_rss_feeds_remove_selected_clicked(self): if len(self.window().edit_channel_rss_feeds_list.selectedItems()) == 0: ConfirmationDialog.show_message( self, "Remove RSS Feeds", "Selection is empty. Please select the feeds to remove.", "OK") return self.dialog = ConfirmationDialog( self, "Remove RSS feed", "Are you sure you want to remove the selected RSS feed?", [('REMOVE', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)]) self.dialog.button_clicked.connect(self.on_rss_feed_dialog_removed) self.dialog.show() def on_rss_feed_dialog_removed(self, action): if action == 0: url = urllib.quote_plus( self.window().edit_channel_rss_feeds_list.selectedItems() [0].text(0)) self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request( "channels/discovered/%s/rssfeeds/%s" % (self.channel_overview["identifier"], url), self.on_rss_feed_removed, method='DELETE') self.dialog.setParent(None) self.dialog = None def on_rss_feed_removed(self, json_result): if json_result['removed']: self.load_channel_rss_feeds() def on_rss_feeds_refresh_clicked(self): self.window().edit_channel_details_rss_refresh_button.setEnabled(False) self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request('channels/discovered/%s/recheckfeeds' % self.channel_overview["identifier"], self.on_rss_feeds_refreshed,\ method='POST') def on_rss_feeds_refreshed(self, json_result): if json_result["rechecked"]: self.window().edit_channel_details_rss_refresh_button.setEnabled( True)
class MarketWalletsPage(QWidget): """ This page displays information about wallets. """ def __init__(self): QWidget.__init__(self) self.request_mgr = None self.initialized = False self.wallets_to_create = [] self.wallets = None self.active_wallet = None self.dialog = None def initialize_wallets_page(self): if not self.initialized: self.window().wallets_back_button.setIcon( QIcon(get_image_path('page_back.png'))) self.window().wallet_btc_overview_button.clicked.connect( lambda: self.initialize_wallet_info( 'BTC', self.window().wallet_btc_overview_button)) self.window().wallet_tbtc_overview_button.clicked.connect( lambda: self.initialize_wallet_info( 'TBTC', self.window().wallet_tbtc_overview_button)) self.window().wallet_mc_overview_button.clicked.connect( lambda: self.initialize_wallet_info( 'MB', self.window().wallet_mc_overview_button)) self.window().wallet_paypal_overview_button.clicked.connect( lambda: self.initialize_wallet_info( 'PP', self.window().wallet_paypal_overview_button)) self.window().wallet_abn_overview_button.clicked.connect( lambda: self.initialize_wallet_info( 'ABNA', self.window().wallet_abn_overview_button)) self.window().wallet_rabo_overview_button.clicked.connect( lambda: self.initialize_wallet_info( 'RABO', self.window().wallet_rabo_overview_button)) self.window().add_wallet_button.clicked.connect( self.on_add_wallet_clicked) self.window().wallet_mc_overview_button.hide() self.window().wallet_btc_overview_button.hide() self.window().wallet_tbtc_overview_button.hide() self.window().wallet_paypal_overview_button.hide() self.window().wallet_abn_overview_button.hide() self.window().wallet_rabo_overview_button.hide() self.window().wallet_info_tabs.hide() self.window().wallet_info_tabs.currentChanged.connect( self.tab_changed) self.initialized = True self.load_wallets() def load_wallets(self): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("wallets", self.on_wallets) def on_wallets(self, wallets): self.wallets = wallets["wallets"] if 'MB' in self.wallets and self.wallets["MB"]["created"]: self.window().wallet_mc_overview_button.show() if 'BTC' in self.wallets and self.wallets["BTC"]["created"]: self.window().wallet_btc_overview_button.show() if 'TBTC' in self.wallets and self.wallets["TBTC"]["created"]: self.window().wallet_tbtc_overview_button.show() if 'PP' in self.wallets and self.wallets["PP"]["created"]: self.window().wallet_paypal_overview_button.show() if 'ABNA' in self.wallets and self.wallets["ABNA"]["created"]: self.window().wallet_abn_overview_button.show() if 'RABO' in self.wallets and self.wallets["RABO"]["created"]: self.window().wallet_rabo_overview_button.show() # Find out which wallets we still can create self.wallets_to_create = [] for identifier, wallet in self.wallets.iteritems(): if not wallet["created"]: self.wallets_to_create.append(identifier) if len(self.wallets_to_create) > 0: self.window().add_wallet_button.setEnabled(True) else: self.window().add_wallet_button.hide() def tab_changed(self, index): if index == 1 and self.active_wallet: self.load_transactions(self.active_wallet) def initialize_wallet_info(self, wallet_id, pressed_button): # Show the tab again self.window().wallet_info_tabs.show() self.window().wallet_management_placeholder_widget.hide() # Clear the selection of all other buttons, except the pressed button for button in self.window().wallet_buttons_container.findChildren( QPushButton): if button != pressed_button: button.setChecked(False) self.active_wallet = wallet_id self.window().wallet_info_tabs.setCurrentIndex(0) self.window().wallet_address_label.setText( self.wallets[wallet_id]['address']) # Create a QR code of the wallet address try: import qrcode qr = qrcode.QRCode( version=1, error_correction=qrcode.constants.ERROR_CORRECT_M, box_size=10, border=5, ) qr.add_data(self.wallets[wallet_id]['address']) qr.make(fit=True) img = qr.make_image() # PIL format qim = ImageQt(img) pixmap = QtGui.QPixmap.fromImage(qim).scaled( 300, 300, QtCore.Qt.KeepAspectRatio) self.window().wallet_address_qr_label.setPixmap(pixmap) except ImportError: self.window().wallet_address_qr_label.setText( "QR Code functionality not available!") def load_transactions(self, wallet_id): self.window().wallet_transactions_list.clear() self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("wallets/%s/transactions" % wallet_id, self.on_transactions) def on_transactions(self, transactions): for transaction in transactions["transactions"]: item = QTreeWidgetItem(self.window().wallet_transactions_list) item.setText(0, "Sent" if transaction["outgoing"] else "Received") item.setText(1, transaction["from"]) item.setText(2, transaction["to"]) item.setText( 3, "%g %s" % (transaction["amount"], transaction["currency"])) item.setText( 4, "%g %s" % (transaction["fee_amount"], transaction["currency"])) item.setText(5, transaction["id"]) timestamp = timestamp_to_time( float(transaction["timestamp"] )) if transaction["timestamp"] != "False" else "-" item.setText(6, timestamp) self.window().wallet_transactions_list.addTopLevelItem(item) def on_add_wallet_clicked(self): menu = TriblerActionMenu(self) for wallet_id in self.wallets_to_create: wallet_action = QAction(self.wallets[wallet_id]['name'], self) wallet_action.triggered.connect( lambda _, wid=wallet_id: self.should_create_wallet(wid)) menu.addAction(wallet_action) menu.exec_(QCursor.pos()) def should_create_wallet(self, wallet_id): if wallet_id == 'BTC' or wallet_id == 'TBTC': self.dialog = ConfirmationDialog( self, "Create Bitcoin wallet", "Please enter the password of your Bitcoin wallet below:", [('CREATE', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)], show_input=True) self.dialog.dialog_widget.dialog_input.setPlaceholderText( 'Wallet password') self.dialog.button_clicked.connect( lambda action: self.on_create_btc_wallet_dialog_done( action, wallet_id)) self.dialog.show() else: self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("wallets/%s" % wallet_id, self.on_wallet_created, method='PUT', data='') def on_create_btc_wallet_dialog_done(self, action, wallet_id): password = self.dialog.dialog_widget.dialog_input.text() if action == 1: # Remove the dialog right now self.dialog.close_dialog() self.dialog = None elif action == 0: self.dialog.buttons[0].setEnabled(False) self.dialog.buttons[1].setEnabled(False) self.dialog.buttons[0].setText("CREATING...") self.request_mgr = TriblerRequestManager() post_data = str("password=%s" % password) self.request_mgr.perform_request("wallets/%s" % wallet_id, self.on_wallet_created, method='PUT', data=post_data) def on_wallet_created(self, response): if self.dialog: self.dialog.close_dialog() self.dialog = None self.load_wallets()
class TriblerWindow(QMainWindow): resize_event = pyqtSignal() escape_pressed = pyqtSignal() received_search_completions = pyqtSignal(object) def on_exception(self, *exc_info): if self.exception_handler_called: # We only show one feedback dialog, even when there are two consecutive exceptions. return self.exception_handler_called = True if self.tray_icon: try: self.tray_icon.deleteLater() except RuntimeError: # The tray icon might have already been removed when unloading Qt. # This is due to the C code actually being asynchronous. logging.debug("Tray icon already removed, no further deletion necessary.") self.tray_icon = None # Stop the download loop self.downloads_page.stop_loading_downloads() # Add info about whether we are stopping Tribler or not os.environ['TRIBLER_SHUTTING_DOWN'] = str(self.core_manager.shutting_down) if not self.core_manager.shutting_down: self.core_manager.stop(stop_app_on_shutdown=False) self.setHidden(True) if self.debug_window: self.debug_window.setHidden(True) exception_text = "".join(traceback.format_exception(*exc_info)) logging.error(exception_text) dialog = FeedbackDialog(self, exception_text, self.core_manager.events_manager.tribler_version, self.start_time) dialog.show() def __init__(self, core_args=None, core_env=None, api_port=None): QMainWindow.__init__(self) QCoreApplication.setOrganizationDomain("nl") QCoreApplication.setOrganizationName("TUDelft") QCoreApplication.setApplicationName("Tribler") QCoreApplication.setAttribute(Qt.AA_UseHighDpiPixmaps) self.gui_settings = QSettings() api_port = api_port or int(get_gui_setting(self.gui_settings, "api_port", DEFAULT_API_PORT)) dispatcher.update_worker_settings(port=api_port) self.navigation_stack = [] self.tribler_started = False self.tribler_settings = None self.debug_window = None self.core_manager = CoreManager(api_port) self.pending_requests = {} self.pending_uri_requests = [] self.download_uri = None self.dialog = None self.new_version_dialog = None self.start_download_dialog_active = False self.request_mgr = None self.search_request_mgr = None self.search_suggestion_mgr = None self.selected_torrent_files = [] self.vlc_available = True self.has_search_results = False self.last_search_query = None self.last_search_time = None self.start_time = time.time() self.exception_handler_called = False self.token_refresh_timer = None sys.excepthook = self.on_exception uic.loadUi(get_ui_file_path('mainwindow.ui'), self) TriblerRequestManager.window = self self.tribler_status_bar.hide() # Load dynamic widgets uic.loadUi(get_ui_file_path('torrent_channel_list_container.ui'), self.channel_page_container) self.channel_torrents_list = self.channel_page_container.items_list self.channel_torrents_detail_widget = self.channel_page_container.details_tab_widget self.channel_torrents_detail_widget.initialize_details_widget() self.channel_torrents_list.itemSelectionChanged.connect(self.channel_page.clicked_item) uic.loadUi(get_ui_file_path('torrent_channel_list_container.ui'), self.search_page_container) self.search_results_list = self.search_page_container.items_list self.search_torrents_detail_widget = self.search_page_container.details_tab_widget self.search_torrents_detail_widget.initialize_details_widget() self.search_results_list.itemClicked.connect(self.on_channel_item_click) self.search_results_list.itemSelectionChanged.connect(self.search_results_page.clicked_item) self.token_balance_widget.mouseReleaseEvent = self.on_token_balance_click def on_state_update(new_state): self.loading_text_label.setText(new_state) self.core_manager.core_state_update.connect(on_state_update) self.magnet_handler = MagnetHandler(self.window) QDesktopServices.setUrlHandler("magnet", self.magnet_handler, "on_open_magnet_link") self.debug_pane_shortcut = QShortcut(QKeySequence("Ctrl+d"), self) self.debug_pane_shortcut.activated.connect(self.clicked_menu_button_debug) # Remove the focus rect on OS X for widget in self.findChildren(QLineEdit) + self.findChildren(QListWidget) + self.findChildren(QTreeWidget): widget.setAttribute(Qt.WA_MacShowFocusRect, 0) self.menu_buttons = [self.left_menu_button_home, self.left_menu_button_search, self.left_menu_button_my_channel, self.left_menu_button_subscriptions, self.left_menu_button_video_player, self.left_menu_button_downloads, self.left_menu_button_discovered] self.video_player_page.initialize_player() self.search_results_page.initialize_search_results_page() self.settings_page.initialize_settings_page() self.subscribed_channels_page.initialize() self.edit_channel_page.initialize_edit_channel_page() self.downloads_page.initialize_downloads_page() self.home_page.initialize_home_page() self.loading_page.initialize_loading_page() self.discovering_page.initialize_discovering_page() self.discovered_page.initialize_discovered_page() self.trust_page.initialize_trust_page() self.stackedWidget.setCurrentIndex(PAGE_LOADING) # Create the system tray icon if QSystemTrayIcon.isSystemTrayAvailable(): self.tray_icon = QSystemTrayIcon() use_monochrome_icon = get_gui_setting(self.gui_settings, "use_monochrome_icon", False, is_bool=True) self.update_tray_icon(use_monochrome_icon) # Create the tray icon menu menu = self.create_add_torrent_menu() show_downloads_action = QAction('Show downloads', self) show_downloads_action.triggered.connect(self.clicked_menu_button_downloads) token_balance_action = QAction('Show token balance', self) token_balance_action.triggered.connect(lambda: self.on_token_balance_click(None)) quit_action = QAction('Quit Tribler', self) quit_action.triggered.connect(self.close_tribler) menu.addSeparator() menu.addAction(show_downloads_action) menu.addAction(token_balance_action) menu.addSeparator() menu.addAction(quit_action) self.tray_icon.setContextMenu(menu) else: self.tray_icon = None self.hide_left_menu_playlist() self.left_menu_button_debug.setHidden(True) self.top_menu_button.setHidden(True) self.left_menu.setHidden(True) self.token_balance_widget.setHidden(True) self.settings_button.setHidden(True) self.add_torrent_button.setHidden(True) self.top_search_bar.setHidden(True) # Set various icons self.top_menu_button.setIcon(QIcon(get_image_path('menu.png'))) self.search_completion_model = QStringListModel() completer = QCompleter() completer.setModel(self.search_completion_model) completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion) self.item_delegate = QStyledItemDelegate() completer.popup().setItemDelegate(self.item_delegate) completer.popup().setStyleSheet(""" QListView { background-color: #404040; } QListView::item { color: #D0D0D0; padding-top: 5px; padding-bottom: 5px; } QListView::item:hover { background-color: #707070; } """) self.top_search_bar.setCompleter(completer) # Toggle debug if developer mode is enabled self.window().left_menu_button_debug.setHidden( not get_gui_setting(self.gui_settings, "debug", False, is_bool=True)) # Start Tribler self.core_manager.start(core_args=core_args, core_env=core_env) self.core_manager.events_manager.received_search_result_channel.connect( self.search_results_page.received_search_result_channel) self.core_manager.events_manager.received_search_result_torrent.connect( self.search_results_page.received_search_result_torrent) self.core_manager.events_manager.torrent_finished.connect(self.on_torrent_finished) self.core_manager.events_manager.new_version_available.connect(self.on_new_version_available) self.core_manager.events_manager.tribler_started.connect(self.on_tribler_started) self.core_manager.events_manager.events_started.connect(self.on_events_started) self.core_manager.events_manager.low_storage_signal.connect(self.on_low_storage) # Install signal handler for ctrl+c events def sigint_handler(*_): self.close_tribler() signal.signal(signal.SIGINT, sigint_handler) self.installEventFilter(self.video_player_page) # Resize the window according to the settings center = QApplication.desktop().availableGeometry(self).center() pos = self.gui_settings.value("pos", QPoint(center.x() - self.width() * 0.5, center.y() - self.height() * 0.5)) size = self.gui_settings.value("size", self.size()) self.move(pos) self.resize(size) self.show() def update_tray_icon(self, use_monochrome_icon): if not QSystemTrayIcon.isSystemTrayAvailable(): return if use_monochrome_icon: self.tray_icon.setIcon(QIcon(QPixmap(get_image_path('monochrome_tribler.png')))) else: self.tray_icon.setIcon(QIcon(QPixmap(get_image_path('tribler.png')))) self.tray_icon.show() 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: """ self.downloads_page.stop_loading_downloads() self.core_manager.stop(False) close_dialog = ConfirmationDialog(self.window(), "<b>CRITICAL ERROR</b>", "You are running low on disk space (<100MB). Please make sure to have " "sufficient free space available and restart Tribler again.", [("Close Tribler", BUTTON_TYPE_NORMAL)]) close_dialog.button_clicked.connect(lambda _: self.close_tribler()) close_dialog.show() def on_torrent_finished(self, torrent_info): if self.tray_icon: self.window().tray_icon.showMessage("Download finished", "Download of %s has finished." % torrent_info["name"]) def show_loading_screen(self): self.top_menu_button.setHidden(True) self.left_menu.setHidden(True) self.token_balance_widget.setHidden(True) self.settings_button.setHidden(True) self.add_torrent_button.setHidden(True) self.top_search_bar.setHidden(True) self.stackedWidget.setCurrentIndex(PAGE_LOADING) def on_tribler_started(self): self.tribler_started = True 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) # fetch the settings, needed for the video player port self.request_mgr = TriblerRequestManager() self.fetch_settings() self.downloads_page.start_loading_downloads() self.home_page.load_popular_torrents() if not self.gui_settings.value("first_discover", False) and not self.core_manager.use_existing_core: self.window().gui_settings.setValue("first_discover", True) self.discovering_page.is_discovering = True self.stackedWidget.setCurrentIndex(PAGE_DISCOVERING) else: self.clicked_menu_button_home() self.setAcceptDrops(True) def on_events_started(self, json_dict): self.setWindowTitle("Tribler %s" % json_dict["version"]) def show_status_bar(self, message): self.tribler_status_bar_label.setText(message) self.tribler_status_bar.show() def hide_status_bar(self): self.tribler_status_bar.hide() def process_uri_request(self): """ Process a URI request if we have one in the queue. """ if len(self.pending_uri_requests) == 0: return uri = self.pending_uri_requests.pop() if uri.startswith('file') or uri.startswith('magnet'): self.start_download_from_uri(uri) def perform_start_download_request(self, uri, anon_download, safe_seeding, destination, selected_files, total_files=0, callback=None): # Check if destination directory is writable if not is_dir_writable(destination): ConfirmationDialog.show_message(self.window(), "Download error <i>%s</i>" % uri, "Insufficient write permissions to <i>%s</i> directory. " "Please add proper write permissions on the directory and " "add the torrent again." % destination, "OK") return selected_files_uri = "" if len(selected_files) != total_files: # Not all files included selected_files_uri = u'&' + u''.join(u"selected_files[]=%s&" % quote_plus_unicode(filename) for filename in selected_files)[:-1] anon_hops = int(self.tribler_settings['download_defaults']['number_hops']) if anon_download else 0 safe_seeding = 1 if safe_seeding else 0 post_data = "uri=%s&anon_hops=%d&safe_seeding=%d&destination=%s%s" % (quote_plus_unicode(uri), anon_hops, safe_seeding, destination, selected_files_uri) post_data = post_data.encode('utf-8') # We need to send bytes in the request, not unicode request_mgr = TriblerRequestManager() request_mgr.perform_request("downloads", callback if callback else self.on_download_added, method='PUT', data=post_data) # Save the download location to the GUI settings current_settings = get_gui_setting(self.gui_settings, "recent_download_locations", "") recent_locations = current_settings.split(",") if len(current_settings) > 0 else [] if isinstance(destination, unicode): destination = destination.encode('utf-8') encoded_destination = destination.encode('hex') if encoded_destination in recent_locations: recent_locations.remove(encoded_destination) recent_locations.insert(0, encoded_destination) if len(recent_locations) > 5: recent_locations = recent_locations[:5] self.gui_settings.setValue("recent_download_locations", ','.join(recent_locations)) def on_new_version_available(self, version): if version == str(self.gui_settings.value('last_reported_version')): return self.new_version_dialog = ConfirmationDialog(self, "New version available", "Version %s of Tribler is available.Do you want to visit the " "website to download the newest version?" % version, [('IGNORE', BUTTON_TYPE_NORMAL), ('LATER', BUTTON_TYPE_NORMAL), ('OK', BUTTON_TYPE_NORMAL)]) self.new_version_dialog.button_clicked.connect(lambda action: self.on_new_version_dialog_done(version, action)) self.new_version_dialog.show() def on_new_version_dialog_done(self, version, action): if action == 0: # ignore self.gui_settings.setValue("last_reported_version", version) elif action == 2: # ok import webbrowser webbrowser.open("https://tribler.org") self.new_version_dialog.close_dialog() self.new_version_dialog = None def on_search_text_change(self, text): self.search_suggestion_mgr = TriblerRequestManager() self.search_suggestion_mgr.perform_request( "search/completions?q=%s" % text, self.on_received_search_completions) def on_received_search_completions(self, completions): if completions is None: return self.received_search_completions.emit(completions) self.search_completion_model.setStringList(completions["completions"]) def fetch_settings(self): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("settings", self.received_settings, capture_errors=False) def received_settings(self, settings): if not settings: return # If we cannot receive the settings, stop Tribler with an option to send the crash report. if 'error' in settings: raise RuntimeError(TriblerRequestManager.get_message_from_error(settings)) self.tribler_settings = settings['settings'] # Set the video server port self.video_player_page.video_player_port = settings["ports"]["video_server~port"] # Disable various components based on the settings if not self.tribler_settings['search_community']['enabled']: self.window().top_search_bar.setHidden(True) if not self.tribler_settings['video_server']['enabled']: self.left_menu_button_video_player.setHidden(True) self.downloads_creditmining_button.setHidden(not self.tribler_settings["credit_mining"]["enabled"]) self.downloads_all_button.click() # process pending file requests (i.e. someone clicked a torrent file when Tribler was closed) # We do this after receiving the settings so we have the default download location. self.process_uri_request() # Set token balance refresh timer and load the token balance self.token_refresh_timer = QTimer() self.token_refresh_timer.timeout.connect(self.load_token_balance) self.token_refresh_timer.start(60000) self.load_token_balance() def on_top_search_button_click(self): current_ts = time.time() current_search_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 self.left_menu_button_search.setChecked(True) self.has_search_results = True self.clicked_menu_button_search() self.search_results_page.perform_search(current_search_query) self.search_request_mgr = TriblerRequestManager() self.search_request_mgr.perform_request("search?q=%s" % current_search_query, None) self.last_search_query = current_search_query self.last_search_time = current_ts def on_settings_button_click(self): self.deselect_all_menu_buttons() self.stackedWidget.setCurrentIndex(PAGE_SETTINGS) self.settings_page.load_settings() self.navigation_stack = [] self.hide_left_menu_playlist() def on_token_balance_click(self, _): self.raise_window() self.deselect_all_menu_buttons() self.stackedWidget.setCurrentIndex(PAGE_TRUST) self.trust_page.load_trust_statistics() self.navigation_stack = [] self.hide_left_menu_playlist() def load_token_balance(self): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("trustchain/statistics", self.received_token_balance, capture_errors=False) def received_token_balance(self, statistics): if not statistics or "statistics" not in statistics: return statistics = statistics["statistics"] if 'latest_block' in statistics: balance = (statistics["latest_block"]["transaction"]["total_up"] - statistics["latest_block"]["transaction"]["total_down"]) self.set_token_balance(balance) else: self.token_balance_label.setText("0 MB") def set_token_balance(self, balance): if abs(balance) > 1024 ** 4: # Balance is over a TB balance /= 1024.0 ** 4 self.token_balance_label.setText("%.1f TB" % balance) elif abs(balance) > 1024 ** 3: # Balance is over a GB balance /= 1024.0 ** 3 self.token_balance_label.setText("%.1f GB" % balance) else: balance /= 1024.0 ** 2 self.token_balance_label.setText("%d MB" % balance) def raise_window(self): self.setWindowState(self.windowState() & ~Qt.WindowMinimized | Qt.WindowActive) self.raise_() self.activateWindow() def create_add_torrent_menu(self): """ Create a menu to add new torrents. Shows when users click on the tray icon or the big plus button. """ menu = TriblerActionMenu(self) browse_files_action = QAction('Import torrent from file', self) browse_directory_action = QAction('Import torrent(s) from directory', self) add_url_action = QAction('Import torrent from magnet/URL', self) browse_files_action.triggered.connect(self.on_add_torrent_browse_file) browse_directory_action.triggered.connect(self.on_add_torrent_browse_dir) add_url_action.triggered.connect(self.on_add_torrent_from_url) menu.addAction(browse_files_action) menu.addAction(browse_directory_action) menu.addAction(add_url_action) return menu def on_add_torrent_button_click(self, pos): self.create_add_torrent_menu().exec_(self.mapToGlobal(self.add_torrent_button.pos())) def on_add_torrent_browse_file(self): filenames = QFileDialog.getOpenFileNames(self, "Please select the .torrent file", QDir.homePath(), "Torrent files (*.torrent)") if len(filenames[0]) > 0: [self.pending_uri_requests.append(u"file:%s" % filename) for filename in filenames[0]] self.process_uri_request() def start_download_from_uri(self, uri): self.download_uri = uri if get_gui_setting(self.gui_settings, "ask_download_settings", True, is_bool=True): # If tribler settings is not available, fetch the settings and inform the user to try again. if not self.tribler_settings: self.fetch_settings() ConfirmationDialog.show_error(self, "Download Error", "Tribler settings is not available yet. " "Fetching it now. Please try again later.") return # Clear any previous dialog if exists if self.dialog: self.dialog.close_dialog() self.dialog = None self.dialog = StartDownloadDialog(self, self.download_uri) self.dialog.button_clicked.connect(self.on_start_download_action) self.dialog.show() self.start_download_dialog_active = True else: # 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'], [], 0) self.process_uri_request() 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()) else: ConfirmationDialog.show_error(self, "Tribler UI Error", "Something went wrong. Please try again.") logging.exception("Error while trying to download. Either dialog or dialog.dialog_widget is None") self.dialog.request_mgr.cancel_request() # To abort the torrent info request 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_add_torrent_browse_dir(self): chosen_dir = QFileDialog.getExistingDirectory(self, "Please select the directory containing the .torrent files", QDir.homePath(), QFileDialog.ShowDirsOnly) if len(chosen_dir) != 0: self.selected_torrent_files = [torrent_file for torrent_file in glob.glob(chosen_dir + "/*.torrent")] self.dialog = ConfirmationDialog(self, "Add torrents from directory", "Are you sure you want to add %d torrents to Tribler?" % len(self.selected_torrent_files), [('ADD', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)]) self.dialog.button_clicked.connect(self.on_confirm_add_directory_dialog) self.dialog.show() def on_confirm_add_directory_dialog(self, action): if action == 0: for torrent_file in self.selected_torrent_files: escaped_uri = u"file:%s" % pathname2url(torrent_file) self.perform_start_download_request(escaped_uri, self.window().tribler_settings['download_defaults'][ 'anonymity_enabled'], self.window().tribler_settings['download_defaults'][ 'safeseeding_enabled'], self.tribler_settings['download_defaults']['saveas'], [], 0) if self.dialog: self.dialog.close_dialog() self.dialog = None def on_add_torrent_from_url(self): # Make sure that the window is visible (this action might be triggered from the tray icon) self.raise_window() if self.video_player_page.isVisible(): # If we're adding a torrent from the video player page, go to the home page. # This is necessary since VLC takes the screen and the popup becomes invisible. self.clicked_menu_button_home() self.dialog = ConfirmationDialog(self, "Add torrent from URL/magnet link", "Please enter the URL/magnet link in the field below:", [('ADD', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)], show_input=True) self.dialog.dialog_widget.dialog_input.setPlaceholderText('URL/magnet link') self.dialog.dialog_widget.dialog_input.setFocus() self.dialog.button_clicked.connect(self.on_torrent_from_url_dialog_done) self.dialog.show() def on_torrent_from_url_dialog_done(self, action): if self.dialog and self.dialog.dialog_widget: uri = self.dialog.dialog_widget.dialog_input.text() # Remove first dialog self.dialog.close_dialog() self.dialog = None if action == 0: self.start_download_from_uri(uri) def on_download_added(self, result): if not result: return if len(self.pending_uri_requests) == 0: # Otherwise, we first process the remaining requests. self.window().left_menu_button_downloads.click() else: self.process_uri_request() def on_top_menu_button_click(self): if self.left_menu.isHidden(): self.left_menu.show() else: self.left_menu.hide() def deselect_all_menu_buttons(self, except_select=None): for button in self.menu_buttons: if button == except_select: button.setEnabled(False) continue button.setEnabled(True) if button == self.left_menu_button_search and not self.has_search_results: button.setEnabled(False) button.setChecked(False) def clicked_menu_button_home(self): self.deselect_all_menu_buttons(self.left_menu_button_home) self.stackedWidget.setCurrentIndex(PAGE_HOME) self.navigation_stack = [] self.hide_left_menu_playlist() def clicked_menu_button_search(self): self.deselect_all_menu_buttons(self.left_menu_button_search) self.stackedWidget.setCurrentIndex(PAGE_SEARCH_RESULTS) self.navigation_stack = [] self.hide_left_menu_playlist() def clicked_menu_button_discovered(self): self.deselect_all_menu_buttons(self.left_menu_button_discovered) self.stackedWidget.setCurrentIndex(PAGE_DISCOVERED) self.discovered_page.load_discovered_channels() self.navigation_stack = [] self.hide_left_menu_playlist() def clicked_menu_button_my_channel(self): self.deselect_all_menu_buttons(self.left_menu_button_my_channel) self.stackedWidget.setCurrentIndex(PAGE_EDIT_CHANNEL) self.edit_channel_page.load_my_channel_overview() self.navigation_stack = [] self.hide_left_menu_playlist() def clicked_menu_button_video_player(self): self.deselect_all_menu_buttons(self.left_menu_button_video_player) self.stackedWidget.setCurrentIndex(PAGE_VIDEO_PLAYER) self.navigation_stack = [] self.show_left_menu_playlist() def clicked_menu_button_downloads(self): self.raise_window() self.left_menu_button_downloads.setChecked(True) self.deselect_all_menu_buttons(self.left_menu_button_downloads) self.stackedWidget.setCurrentIndex(PAGE_DOWNLOADS) self.navigation_stack = [] self.hide_left_menu_playlist() def clicked_menu_button_debug(self): if not self.debug_window: self.debug_window = DebugWindow(self.tribler_settings, self.core_manager.events_manager.tribler_version) self.debug_window.show() def clicked_menu_button_subscriptions(self): self.deselect_all_menu_buttons(self.left_menu_button_subscriptions) self.subscribed_channels_page.load_subscribed_channels() self.stackedWidget.setCurrentIndex(PAGE_SUBSCRIBED_CHANNELS) self.navigation_stack = [] self.hide_left_menu_playlist() def hide_left_menu_playlist(self): self.left_menu_seperator.setHidden(True) self.left_menu_playlist_label.setHidden(True) self.left_menu_playlist.setHidden(True) def show_left_menu_playlist(self): self.left_menu_seperator.setHidden(False) self.left_menu_playlist_label.setHidden(False) self.left_menu_playlist.setHidden(False) def on_channel_item_click(self, channel_list_item): list_widget = channel_list_item.listWidget() from TriblerGUI.widgets.channel_list_item import ChannelListItem if isinstance(list_widget.itemWidget(channel_list_item), ChannelListItem): channel_info = channel_list_item.data(Qt.UserRole) self.channel_page.initialize_with_channel(channel_info) self.navigation_stack.append(self.stackedWidget.currentIndex()) self.stackedWidget.setCurrentIndex(PAGE_CHANNEL_DETAILS) def on_playlist_item_click(self, playlist_list_item): list_widget = playlist_list_item.listWidget() from TriblerGUI.widgets.playlist_list_item import PlaylistListItem if isinstance(list_widget.itemWidget(playlist_list_item), PlaylistListItem): playlist_info = playlist_list_item.data(Qt.UserRole) self.playlist_page.initialize_with_playlist(playlist_info) self.navigation_stack.append(self.stackedWidget.currentIndex()) self.stackedWidget.setCurrentIndex(PAGE_PLAYLIST_DETAILS) def on_page_back_clicked(self): try: prev_page = self.navigation_stack.pop() self.stackedWidget.setCurrentIndex(prev_page) if prev_page == PAGE_SEARCH_RESULTS: self.stackedWidget.widget(prev_page).load_search_results_in_list() if prev_page == PAGE_SUBSCRIBED_CHANNELS: self.stackedWidget.widget(prev_page).load_subscribed_channels() if prev_page == PAGE_DISCOVERED: self.stackedWidget.widget(prev_page).load_discovered_channels() except IndexError: logging.exception("Unknown page found in stack") def on_edit_channel_clicked(self): self.stackedWidget.setCurrentIndex(PAGE_EDIT_CHANNEL) self.navigation_stack = [] self.channel_page.on_edit_channel_clicked() def resizeEvent(self, _): # Resize home page cells cell_width = self.home_page_table_view.width() / 3 - 3 # We have some padding to the right cell_height = cell_width / 2 + 60 for i in range(0, 3): self.home_page_table_view.setColumnWidth(i, cell_width) self.home_page_table_view.setRowHeight(i, cell_height) self.resize_event.emit() def exit_full_screen(self): self.top_bar.show() self.left_menu.show() self.video_player_page.is_full_screen = False self.showNormal() def close_tribler(self): if not self.core_manager.shutting_down: def show_force_shutdown(): self.loading_text_label.setText("Tribler is taking longer than expected to shut down. You can force " "Tribler to shutdown by pressing the button below. This might lead " "to data loss.") self.window().force_shutdown_btn.show() if self.tray_icon: self.tray_icon.deleteLater() self.show_loading_screen() self.hide_status_bar() self.loading_text_label.setText("Shutting down...") self.shutdown_timer = QTimer() self.shutdown_timer.timeout.connect(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_queue.clear() # Stop the token balance timer if self.token_refresh_timer: self.token_refresh_timer.stop() def closeEvent(self, close_event): self.close_tribler() close_event.ignore() def keyReleaseEvent(self, event): if event.key() == Qt.Key_Escape: self.escape_pressed.emit() if self.isFullScreen(): self.exit_full_screen() def dragEnterEvent(self, e): file_urls = [_qurl_to_path(url) for url in e.mimeData().urls()] if e.mimeData().hasUrls() else [] if any(os.path.isfile(filename) for filename in file_urls): e.accept() else: e.ignore() def dropEvent(self, e): file_urls = ([(_qurl_to_path(url), url.toString()) for url in e.mimeData().urls()] if e.mimeData().hasUrls() else []) for filename, fileurl in file_urls: if os.path.isfile(filename): self.start_download_from_uri(fileurl) e.accept() def clicked_force_shutdown(self): process_checker = ProcessChecker() if process_checker.already_running: core_pid = process_checker.get_pid_from_lock_file() os.kill(int(core_pid), 9) # Stop the Qt application QApplication.quit()
class CreateTorrentPage(QWidget): """ The CreateTorrentPage is the page where users can create torrent files so they can be added to their channel. """ def __init__(self): QWidget.__init__(self) self.channel_identifier = None self.request_mgr = None self.dialog = None self.selected_item_index = -1 self.initialized = False def initialize(self, identifier): self.channel_identifier = identifier self.window().create_torrent_name_field.setText('') self.window().create_torrent_description_field.setText('') self.window().create_torrent_files_list.clear() self.window().seed_after_adding_checkbox.setChecked(True) self.window().edit_channel_create_torrent_progress_label.hide() if not self.initialized: self.window().manage_channel_create_torrent_back.setIcon( QIcon(get_image_path('page_back.png'))) self.window( ).create_torrent_files_list.customContextMenuRequested.connect( self.on_right_click_file_item) self.window().manage_channel_create_torrent_back.clicked.connect( self.on_create_torrent_manage_back_clicked) self.window().create_torrent_choose_files_button.clicked.connect( self.on_choose_files_clicked) self.window().create_torrent_choose_dir_button.clicked.connect( self.on_choose_dir_clicked) self.window().edit_channel_create_torrent_button.clicked.connect( self.on_create_clicked) self.initialized = True def on_create_torrent_manage_back_clicked(self): self.window().edit_channel_details_stacked_widget.setCurrentIndex( PAGE_EDIT_CHANNEL_TORRENTS) def on_choose_files_clicked(self): filenames, _ = QFileDialog.getOpenFileNames(self.window(), "Please select the files", "") for filename in filenames: self.window().create_torrent_files_list.addItem(filename) def on_choose_dir_clicked(self): chosen_dir = QFileDialog.getExistingDirectory( self.window(), "Please select the directory containing the files", "", QFileDialog.ShowDirsOnly) if len(chosen_dir) == 0: return files = [] for path, _, dir_files in os.walk(chosen_dir): for filename in dir_files: files.append(os.path.join(path, filename)) self.window().create_torrent_files_list.clear() for filename in files: self.window().create_torrent_files_list.addItem(filename) def on_create_clicked(self): if self.window().create_torrent_files_list.count() == 0: self.dialog = ConfirmationDialog( self, "Notice", "You should add at least one file to your torrent.", [('CLOSE', BUTTON_TYPE_NORMAL)]) self.dialog.button_clicked.connect(self.on_dialog_ok_clicked) self.dialog.show() return files_str = u"" for ind in xrange(self.window().create_torrent_files_list.count()): files_str += u"files[]=%s&" % urllib.quote_plus(self.window( ).create_torrent_files_list.item(ind).text().encode('utf-8')) name = urllib.quote_plus( self.window().create_torrent_name_field.text().encode('utf-8')) description = urllib.quote_plus(self.window( ).create_torrent_description_field.toPlainText().encode('utf-8')) post_data = (u"%s&name=%s&description=%s" % (files_str[:-1], name, description)).encode('utf-8') url = "createtorrent?download=1" if self.window( ).seed_after_adding_checkbox.isChecked() else "createtorrent" self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request(url, self.on_torrent_created, data=post_data, method='POST') # Show creating torrent text self.window().edit_channel_create_torrent_progress_label.show() def on_dialog_ok_clicked(self, _): self.dialog.setParent(None) self.dialog = None def on_torrent_created(self, result): if 'torrent' in result: self.add_torrent_to_channel(result['torrent']) def add_torrent_to_channel(self, torrent): post_data = str("torrent=%s" % urllib.quote_plus(torrent)) self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("channels/discovered/%s/torrents" % self.channel_identifier, self.on_torrent_to_channel_added, data=post_data, method='PUT') def on_torrent_to_channel_added(self, result): self.window().edit_channel_create_torrent_progress_label.hide() if 'added' in result: self.window().edit_channel_details_stacked_widget.setCurrentIndex( PAGE_EDIT_CHANNEL_TORRENTS) self.window().edit_channel_page.load_channel_torrents() def on_remove_entry(self): self.window().create_torrent_files_list.takeItem( self.selected_item_index) def on_right_click_file_item(self, pos): item_clicked = self.window().create_torrent_files_list.itemAt(pos) if not item_clicked: return self.selected_item_index = self.window().create_torrent_files_list.row( item_clicked) menu = TriblerActionMenu(self) remove_action = QAction('Remove file', self) remove_action.triggered.connect(self.on_remove_entry) menu.addAction(remove_action) menu.exec_(self.window().create_torrent_files_list.mapToGlobal(pos))
class CreateTorrentPage(QWidget): """ The CreateTorrentPage is the page where users can create torrent files so they can be added to their channel. """ def __init__(self): QWidget.__init__(self) self.channel_identifier = None self.request_mgr = None self.dialog = None self.selected_item_index = -1 self.initialized = False def initialize(self): self.window().create_torrent_name_field.setText('') self.window().create_torrent_description_field.setText('') self.window().create_torrent_files_list.clear() self.window().seed_after_adding_checkbox.setChecked(True) self.window().edit_channel_create_torrent_progress_label.hide() if not self.initialized: self.window().manage_channel_create_torrent_back.setIcon(QIcon(get_image_path('page_back.png'))) self.window().create_torrent_files_list.customContextMenuRequested.connect(self.on_right_click_file_item) self.window().manage_channel_create_torrent_back.clicked.connect(self.on_create_torrent_manage_back_clicked) self.window().create_torrent_choose_files_button.clicked.connect(self.on_choose_files_clicked) self.window().create_torrent_choose_dir_button.clicked.connect(self.on_choose_dir_clicked) self.window().edit_channel_create_torrent_button.clicked.connect(self.on_create_clicked) self.initialized = True def on_create_torrent_manage_back_clicked(self): self.window().edit_channel_details_stacked_widget.setCurrentIndex(PAGE_EDIT_CHANNEL_TORRENTS) def on_choose_files_clicked(self): filenames, _ = QFileDialog.getOpenFileNames(self.window(), "Please select the files", QDir.homePath()) for filename in filenames: self.window().create_torrent_files_list.addItem(filename) def on_choose_dir_clicked(self): chosen_dir = QFileDialog.getExistingDirectory(self.window(), "Please select the directory containing the files", "", QFileDialog.ShowDirsOnly) if len(chosen_dir) == 0: return files = [] for path, _, dir_files in os.walk(chosen_dir): for filename in dir_files: files.append(os.path.join(path, filename)) self.window().create_torrent_files_list.clear() for filename in files: self.window().create_torrent_files_list.addItem(filename) def on_create_clicked(self): if self.window().create_torrent_files_list.count() == 0: self.dialog = ConfirmationDialog(self, "Notice", "You should add at least one file to your torrent.", [('CLOSE', BUTTON_TYPE_NORMAL)]) self.dialog.button_clicked.connect(self.on_dialog_ok_clicked) self.dialog.show() return self.window().edit_channel_create_torrent_button.setEnabled(False) files_list = [] for ind in xrange(self.window().create_torrent_files_list.count()): file_str = self.window().create_torrent_files_list.item(ind).text() files_list.append(file_str) name = self.window().create_torrent_name_field.text() description = self.window().create_torrent_description_field.toPlainText() post_data = { "name": name, "description": description, "files": files_list } url = "createtorrent?download=1" if self.window().seed_after_adding_checkbox.isChecked() else "createtorrent" self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request(url, self.on_torrent_created, data=post_data, method='POST') # Show creating torrent text self.window().edit_channel_create_torrent_progress_label.show() def on_dialog_ok_clicked(self, _): self.dialog.close_dialog() self.dialog = None def on_torrent_created(self, result): if not result: return self.window().edit_channel_create_torrent_button.setEnabled(True) if 'torrent' in result: self.add_torrent_to_channel(result['torrent']) def add_torrent_to_channel(self, torrent): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("mychannel/torrents", self.on_torrent_to_channel_added, data={"torrent": torrent}, method='PUT') def on_torrent_to_channel_added(self, result): if not result: return self.window().edit_channel_create_torrent_progress_label.hide() if 'added' in result: self.window().edit_channel_details_stacked_widget.setCurrentIndex(PAGE_EDIT_CHANNEL_TORRENTS) self.window().edit_channel_page.load_my_torrents() def on_remove_entry(self): self.window().create_torrent_files_list.takeItem(self.selected_item_index) def on_right_click_file_item(self, pos): item_clicked = self.window().create_torrent_files_list.itemAt(pos) if not item_clicked: return self.selected_item_index = self.window().create_torrent_files_list.row(item_clicked) menu = TriblerActionMenu(self) remove_action = QAction('Remove file', self) remove_action.triggered.connect(self.on_remove_entry) menu.addAction(remove_action) menu.exec_(self.window().create_torrent_files_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.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))
class MyChannelPage(QWidget): """ This class is responsible for managing lists and data on the your channel page, including torrents, playlists and rss feeds. """ def initialize_my_channel_page(self): self.my_channel_stacked_widget = self.findChild(QStackedWidget, "my_channel_stacked_widget") self.my_channel_details_stacked_widget = self.findChild(QStackedWidget, "my_channel_details_stacked_widget") self.create_channel_form = self.findChild(QWidget, "create_channel_form") self.create_new_channel_intro_label = self.findChild(QLabel, "create_new_channel_intro_label") self.create_channel_intro_button = self.findChild(QPushButton, "create_channel_intro_button") self.create_channel_intro_button.clicked.connect(self.on_create_channel_intro_button_clicked) self.my_channel_name_label = self.findChild(QLabel, "my_channel_name_label") self.my_channel_description_label = self.findChild(QLabel, "my_channel_description_label") self.my_channel_identifier_label = self.findChild(QLabel, "my_channel_identifier_label") self.my_channel_name_input = self.findChild(QLineEdit, "my_channel_name_input") self.my_channel_description_input = self.findChild(QTextEdit, "my_channel_description_input") self.my_channel_torrents_list = self.findChild(QTreeWidget, "my_channel_torrents_list") self.my_channel_rss_feeds_list = self.findChild(QTreeWidget, "my_channel_rss_feeds_list") self.create_channel_intro_button_container = self.findChild(QWidget, "create_channel_intro_button_container") self.create_channel_form.hide() self.my_channel_stacked_widget.setCurrentIndex(1) self.my_channel_details_stacked_widget.setCurrentIndex(PAGE_MY_CHANNEL_OVERVIEW) self.channel_settings_page = self.findChild(QWidget, "channel_settings_page") self.my_channel_torrents_remove_selected_button = self.findChild(QToolButton, "my_channel_torrents_remove_selected_button") self.my_channel_torrents_remove_selected_button.clicked.connect(self.on_torrents_remove_selected_clicked) self.my_channel_torrents_remove_all_button = self.findChild(QToolButton, "my_channel_torrents_remove_all_button") self.my_channel_torrents_remove_all_button.clicked.connect(self.on_torrents_remove_all_clicked) self.my_channel_torrents_export_button = self.findChild(QToolButton, "my_channel_torrents_export_button") self.my_channel_torrents_export_button.clicked.connect(self.on_torrents_export_clicked) # Tab bar buttons self.channel_settings_tab = self.findChild(QWidget, "channel_settings_tab") self.channel_settings_tab.initialize() self.channel_settings_tab.clicked_tab_button.connect(self.clicked_tab_button) # add some dummy items to rss feeds list for i in range(0, 10): item = QTreeWidgetItem(self.my_channel_rss_feeds_list) item.setText(0, "http://fancyurl.com/rss_feed.xml") self.my_channel_rss_feeds_list.addTopLevelItem(item) def load_my_channel_overview(self): self.mychannel_request_mgr = TriblerRequestManager() self.mychannel_request_mgr.get_my_channel_overview(self.initialize_with_overview) def initialize_with_overview(self, overview): self.my_channel_overview = overview self.my_channel_name_label.setText(overview["name"]) self.my_channel_description_label.setText(overview["description"]) self.my_channel_identifier_label.setText(overview["identifier"]) self.my_channel_name_input.setText(overview["name"]) self.my_channel_description_input.setText(overview["description"]) def load_my_channel_torrents(self): self.mychannel_request_mgr = TriblerRequestManager() self.mychannel_request_mgr.get_my_channel_torrents(self.initialize_with_torrents) def initialize_with_torrents(self, torrents): self.my_channel_torrents_list.clear() for torrent in torrents: item = QTreeWidgetItem(self.my_channel_torrents_list) item.setText(0, torrent["name"]) item.setText(1, str(torrent["added"])) self.my_channel_torrents_list.addTopLevelItem(item) def load_my_channel_rss_feeds(self): self.mychannel_request_mgr = TriblerRequestManager() self.mychannel_request_mgr.get_my_channel_rss_feeds(self.initialize_with_rss_feeds) def initialize_with_rss_feeds(self, rss_feeds): self.my_channel_rss_feeds_list.clear() for feed in rss_feeds: item = QTreeWidgetItem(self.my_channel_rss_feeds_list) item.setText(0, feed["url"]) self.my_channel_rss_feeds_list.addTopLevelItem(item) def on_torrents_remove_selected_clicked(self): num_selected = len(self.my_channel_torrents_list.selectedItems()) if num_selected == 0: return self.dialog = ConfirmationDialog(self, "Remove %s selected torrents" % num_selected, "Are you sure that you want to remove %s selected torrents from your channel?" % num_selected) self.dialog.button_clicked.connect(self.on_torrents_remove_selected_action) self.dialog.show() def on_torrents_remove_all_clicked(self): self.dialog = ConfirmationDialog(self.window(), "Remove all torrents", "Are you sure that you want to remove all torrents from your channel? You cannot undo this action.") self.dialog.button_clicked.connect(self.on_torrents_remove_all_action) self.dialog.show() def on_torrents_export_clicked(self): selected_dir = QFileDialog.getExistingDirectory(self, "Choose a directory to export the torrent files to") # TODO Martijn: actually export the .torrent files def on_torrents_remove_selected_action(self, result): self.dialog.setParent(None) self.dialog = None def on_torrents_remove_all_action(self, result): self.dialog.setParent(None) self.dialog = None def clicked_tab_button(self, tab_button_name): if tab_button_name == "my_channel_overview_button": self.my_channel_details_stacked_widget.setCurrentIndex(PAGE_MY_CHANNEL_OVERVIEW) elif tab_button_name == "my_channel_settings_button": self.my_channel_details_stacked_widget.setCurrentIndex(PAGE_MY_CHANNEL_SETTINGS) elif tab_button_name == "my_channel_torrents_button": self.my_channel_details_stacked_widget.setCurrentIndex(PAGE_MY_CHANNEL_TORRENTS) self.load_my_channel_torrents() elif tab_button_name == "my_channel_playlists_button": self.my_channel_details_stacked_widget.setCurrentIndex(PAGE_MY_CHANNEL_PLAYLISTS) elif tab_button_name == "my_channel_rss_feeds_button": self.my_channel_details_stacked_widget.setCurrentIndex(PAGE_MY_CHANNEL_RSS_FEEDS) self.load_my_channel_rss_feeds() def on_create_channel_intro_button_clicked(self): self.create_channel_form.show() self.create_channel_intro_button_container.hide() self.create_new_channel_intro_label.setText("Please enter your channel details below.")
class MarketOrdersPage(QWidget): """ This page displays orders in the decentralized market in Tribler. """ def __init__(self): QWidget.__init__(self) self.request_mgr = None self.initialized = False self.selected_item = None self.dialog = None self.wallets = {} def initialize_orders_page(self, wallets): if not self.initialized: self.window().orders_back_button.setIcon( QIcon(get_image_path('page_back.png'))) self.window().market_orders_list.sortItems(0, Qt.AscendingOrder) self.window( ).market_orders_list.customContextMenuRequested.connect( self.on_right_click_order) self.initialized = True self.wallets = wallets self.load_orders() def load_orders(self): self.window().market_orders_list.clear() self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("market/orders", self.on_received_orders) def on_received_orders(self, orders): for order in orders["orders"]: if self.wallets: asset1_prec = self.wallets[order["assets"]["first"] ["type"]]["precision"] asset2_prec = self.wallets[order["assets"]["second"] ["type"]]["precision"] item = OrderWidgetItem(self.window().market_orders_list, order, asset1_prec, asset2_prec) self.window().market_orders_list.addTopLevelItem(item) def on_right_click_order(self, pos): item_clicked = self.window().market_orders_list.itemAt(pos) if not item_clicked: return self.selected_item = item_clicked if self.selected_item.order[ 'status'] == 'open': # We can only cancel an open order menu = TriblerActionMenu(self) cancel_action = QAction('Cancel order', self) cancel_action.triggered.connect(self.on_cancel_order_clicked) menu.addAction(cancel_action) menu.exec_(self.window().market_orders_list.mapToGlobal(pos)) def on_cancel_order_clicked(self): self.dialog = ConfirmationDialog( self, "Cancel order", "Are you sure you want to cancel the order with id %s?" % self.selected_item.order['order_number'], [('NO', BUTTON_TYPE_NORMAL), ('YES', BUTTON_TYPE_CONFIRM)]) self.dialog.button_clicked.connect(self.on_confirm_cancel_order) self.dialog.show() def on_confirm_cancel_order(self, action): if action == 1: self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request( "market/orders/%s/cancel" % self.selected_item.order['order_number'], self.on_order_cancelled, method='POST') self.dialog.close_dialog() self.dialog = None def on_order_cancelled(self, response): self.load_orders()
class EditChannelPage(QWidget): """ This class is responsible for managing lists and data on your channel page """ on_torrents_removed = pyqtSignal(list) on_all_torrents_removed = pyqtSignal() on_commit = pyqtSignal() def __init__(self): QWidget.__init__(self) self.channel_overview = None self.chosen_dir = None self.dialog = None self.editchannel_request_mgr = None self.model = None self.controller = None self.channel_dirty = False self.gui_settings = None self.commit_timer = None self.autocommit_enabled = None def initialize_edit_channel_page(self, gui_settings): self.gui_settings = gui_settings self.window().create_channel_intro_button.clicked.connect( self.on_create_channel_intro_button_clicked) self.window().create_channel_form.hide() self.update_channel_commit_views() self.window().edit_channel_stacked_widget.setCurrentIndex(1) self.window().edit_channel_details_stacked_widget.setCurrentIndex( PAGE_EDIT_CHANNEL_OVERVIEW) self.window().create_channel_button.clicked.connect( self.on_create_channel_button_pressed) self.window().edit_channel_save_button.clicked.connect( self.on_edit_channel_save_button_pressed) self.window().edit_channel_commit_button.clicked.connect( self.clicked_edit_channel_commit_button) # Tab bar buttons self.window().channel_settings_tab.initialize() self.window().channel_settings_tab.clicked_tab_button.connect( self.clicked_tab_button) self.window().export_channel_button.clicked.connect( self.on_export_mdblob) # TODO: re-enable remove_selected button self.window().remove_selected_button.setHidden(True) # Connect torrent addition/removal buttons self.window().remove_selected_button.clicked.connect( self.on_torrents_remove_selected_clicked) self.window().remove_all_button.clicked.connect( self.on_torrents_remove_all_clicked) self.window().add_button.clicked.connect(self.on_torrents_add_clicked) self.model = MyTorrentsContentModel() self.controller = MyTorrentsTableViewController( self.model, self.window().edit_channel_torrents_container.content_table, self.window().edit_channel_torrents_container.details_container, self.window().edit_channel_torrents_num_items_label, self.window().edit_channel_torrents_filter) self.window().edit_channel_torrents_container.details_container.hide() self.autocommit_enabled = get_gui_setting( self.gui_settings, "autocommit_enabled", True, is_bool=True) if self.gui_settings else True # Commit the channel just in case there are uncommitted changes left since the last time (e.g. Tribler crashed) # The timer thing here is a workaround for race condition with the core startup if self.autocommit_enabled: if not self.commit_timer: self.commit_timer = QTimer() self.commit_timer.setSingleShot(True) self.commit_timer.timeout.connect(self.autocommit_fired) self.controller.table_view.setColumnHidden(3, True) self.model.exclude_deleted = True self.commit_timer.stop() self.commit_timer.start(10000) else: self.controller.table_view.setColumnHidden(4, True) self.model.exclude_deleted = False def update_channel_commit_views(self, deleted_index=None): if self.channel_dirty and self.autocommit_enabled: self.commit_timer.stop() self.commit_timer.start(CHANNEL_COMMIT_DELAY) if deleted_index: # TODO: instead of reloading the whole table, just remove the deleted row and update start and end self.load_my_torrents() self.window().commit_control_bar.setHidden(not self.channel_dirty or self.autocommit_enabled) def load_my_channel_overview(self): if not self.channel_overview: self.window().edit_channel_stacked_widget.setCurrentIndex(2) self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request( "mychannel", self.initialize_with_channel_overview, capture_errors=False) def initialize_with_channel_overview(self, overview): if not overview: return if 'error' in overview: self.window().edit_channel_stacked_widget.setCurrentIndex(0) return self.channel_overview = overview["mychannel"] self.channel_dirty = self.channel_overview['dirty'] self.update_channel_commit_views() self.window().export_channel_button.setHidden(False) self.window().edit_channel_name_label.setText("My channel") self.window().edit_channel_overview_name_label.setText( self.channel_overview["name"]) self.window().edit_channel_description_label.setText( self.channel_overview["description"]) self.window().edit_channel_identifier_label.setText( self.channel_overview["public_key"]) self.window().edit_channel_name_edit.setText( self.channel_overview["name"]) self.window().edit_channel_description_edit.setText( self.channel_overview["description"]) self.window().edit_channel_stacked_widget.setCurrentIndex(1) self.model.channel_pk = self.channel_overview["public_key"] def on_create_channel_button_pressed(self): channel_name = self.window().new_channel_name_edit.text() channel_description = self.window( ).new_channel_description_edit.toPlainText() if len(channel_name) == 0: self.window().new_channel_name_label.setStyleSheet("color: red;") return post_data = {"name": channel_name, "description": channel_description} self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request("mychannel", self.on_channel_created, data=post_data, method='PUT') def on_channel_created(self, result): if not result: return if u'added' in result: self.window().create_channel_button.setEnabled(True) self.load_my_channel_overview() def on_edit_channel_save_button_pressed(self): channel_name = self.window().edit_channel_name_edit.text() channel_description = self.window( ).edit_channel_description_edit.toPlainText() post_data = {"name": channel_name, "description": channel_description} self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request("mychannel", self.on_channel_edited, data=post_data, method='POST') def on_channel_edited(self, result): if not result: return if 'modified' in result: self.window().edit_channel_name_label.setText( self.window().edit_channel_name_edit.text()) self.window().edit_channel_description_label.setText( self.window().edit_channel_description_edit.toPlainText()) def clicked_tab_button(self, tab_button_name): if tab_button_name == "edit_channel_overview_button": self.window().edit_channel_details_stacked_widget.setCurrentIndex( PAGE_EDIT_CHANNEL_OVERVIEW) elif tab_button_name == "edit_channel_settings_button": self.window().edit_channel_details_stacked_widget.setCurrentIndex( PAGE_EDIT_CHANNEL_SETTINGS) elif tab_button_name == "edit_channel_torrents_button": self.load_my_torrents() self.window().edit_channel_details_stacked_widget.setCurrentIndex( PAGE_EDIT_CHANNEL_TORRENTS) def load_my_torrents(self): self.controller.model.reset() self.controller.perform_query(first=1, last=50) # Load the first 50 torrents def on_create_channel_intro_button_clicked(self): self.window().create_channel_form.show() self.window().create_channel_intro_button_container.hide() self.window().create_new_channel_intro_label.setText( "Please enter your channel details below.") def on_export_mdblob(self): export_dir = QFileDialog.getExistingDirectory( self, "Please select the destination directory", "", QFileDialog.ShowDirsOnly) if len(export_dir) == 0: return # Show confirmation dialog where we specify the name of the file mdblob_name = self.channel_overview["public_key"] dialog = ConfirmationDialog( self, "Export mdblob file", "Please enter the name of the channel metadata file:", [('SAVE', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)], show_input=True) 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_request_done(dest_path, data): 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) dialog.dialog_widget.dialog_input.setPlaceholderText( 'Channel file name') dialog.dialog_widget.dialog_input.setText("%s.mdblob" % mdblob_name) dialog.dialog_widget.dialog_input.setFocus() dialog.button_clicked.connect(on_export_download_dialog_done) dialog.show() # Torrent removal-related methods def on_torrents_remove_selected_clicked(self): selected_items = self.controller.table_view.selectedIndexes() num_selected = len(selected_items) if num_selected == 0: return selected_infohashes = [ self.model.data_items[row][u'infohash'] for row in set([index.row() for index in selected_items]) ] self.dialog = ConfirmationDialog( self, "Remove %s selected torrents" % len(selected_infohashes), "Are you sure that you want to remove %s selected torrents " "from your channel?" % len(selected_infohashes), [('CONFIRM', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)]) self.dialog.button_clicked.connect( lambda action: self.on_torrents_remove_selected_action( action, selected_infohashes)) self.dialog.show() def on_torrents_remove_selected_action(self, action, items): if action == 0: items = [str(item) for item in items] infohashes = ",".join(items) post_data = { "infohashes": infohashes, "status": COMMIT_STATUS_TODELETE } request_mgr = TriblerRequestManager() request_mgr.perform_request( "mychannel/torrents", lambda response: self.on_torrents_removed_response( response, items), data=post_data, method='POST') if self.dialog: self.dialog.close_dialog() self.dialog = None def on_torrents_removed_response(self, json_result, infohashes): if not json_result: return if 'success' in json_result and json_result['success']: self.on_torrents_removed.emit(infohashes) self.load_my_torrents() def on_torrents_remove_all_clicked(self): self.dialog = ConfirmationDialog( self.window(), "Remove all torrents", "Are you sure that you want to remove all torrents from your channel?", [('CONFIRM', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)]) self.dialog.button_clicked.connect(self.on_torrents_remove_all_action) self.dialog.show() def on_torrents_remove_all_action(self, action): if action == 0: request_mgr = TriblerRequestManager() request_mgr.perform_request("mychannel/torrents", self.on_all_torrents_removed_response, method='DELETE') self.dialog.close_dialog() self.dialog = None def on_all_torrents_removed_response(self, json_result): if not json_result: return if 'success' in json_result and json_result['success']: self.on_all_torrents_removed.emit() self.load_my_torrents() # Torrent addition-related methods def on_add_torrents_browse_dir(self): chosen_dir = QFileDialog.getExistingDirectory( self, "Please select the directory containing the .torrent files", QDir.homePath(), QFileDialog.ShowDirsOnly) if not chosen_dir: return self.chosen_dir = chosen_dir self.dialog = ConfirmationDialog( self, "Add torrents from directory", "Add all torrent files from the following directory " "to your Tribler channel:\n\n%s" % chosen_dir, [('ADD', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)], checkbox_text="Include subdirectories (recursive mode)") self.dialog.button_clicked.connect( self.on_confirm_add_directory_dialog) self.dialog.show() def on_confirm_add_directory_dialog(self, action): if action == 0: self.add_dir_to_channel(self.chosen_dir, recursive=self.dialog.checkbox.isChecked()) if self.dialog: self.dialog.close_dialog() self.dialog = None self.chosen_dir = None def on_torrents_add_clicked(self): menu = TriblerActionMenu(self) browse_files_action = QAction('Import torrent from file', self) browse_dir_action = QAction('Import torrent(s) from dir', self) add_url_action = QAction('Add torrent from URL/magnet link', self) create_torrent_action = QAction('Create torrent from file(s)', self) browse_files_action.triggered.connect(self.on_add_torrent_browse_file) browse_dir_action.triggered.connect(self.on_add_torrents_browse_dir) add_url_action.triggered.connect(self.on_add_torrent_from_url) create_torrent_action.triggered.connect( self.on_create_torrent_from_files) menu.addAction(browse_files_action) menu.addAction(browse_dir_action) menu.addAction(add_url_action) menu.addAction(create_torrent_action) menu.exec_(QCursor.pos()) def on_create_torrent_from_files(self): self.window().edit_channel_details_create_torrent.initialize() self.window().edit_channel_details_stacked_widget.setCurrentIndex( PAGE_EDIT_CHANNEL_CREATE_TORRENT) def on_add_torrent_browse_file(self): filenames = QFileDialog.getOpenFileNames( self, "Please select the .torrent file", "", "Torrent files (*.torrent)") if not filenames[0]: return for filename in filenames[0]: self.add_torrent_to_channel(filename) def on_add_torrent_from_url(self): self.dialog = ConfirmationDialog( self, "Add torrent from URL/magnet link", "Please enter the URL/magnet link in the field below:", [('ADD', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)], show_input=True) self.dialog.dialog_widget.dialog_input.setPlaceholderText( 'URL/magnet link') self.dialog.button_clicked.connect( self.on_torrent_from_url_dialog_done) self.dialog.show() def on_torrent_from_url_dialog_done(self, action): if action == 0: self.add_torrent_url_to_channel( self.dialog.dialog_widget.dialog_input.text()) self.dialog.close_dialog() self.dialog = None def autocommit_fired(self): def commit_channel(overview): try: if overview and overview['mychannel']['dirty']: self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request( "mychannel/commit", lambda _: None, method='POST', capture_errors=False) except KeyError: return if self.channel_overview: self.clicked_edit_channel_commit_button() else: self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request("mychannel", commit_channel, capture_errors=False) # Commit button-related methods def clicked_edit_channel_commit_button(self): request_mgr = TriblerRequestManager() request_mgr.perform_request("mychannel/commit", self.on_channel_committed, method='POST') def on_channel_committed(self, result): if not result: return if 'success' in result and result['success']: self.channel_dirty = False self.update_channel_commit_views() self.on_commit.emit() if not self.autocommit_enabled: self.load_my_torrents() def add_torrent_to_channel(self, filename): with open(filename, "rb") as torrent_file: torrent_content = b64encode(torrent_file.read()) request_mgr = TriblerRequestManager() request_mgr.perform_request("mychannel/torrents", self.on_torrent_to_channel_added, method='PUT', data={"torrent": torrent_content}) def add_dir_to_channel(self, dirname, recursive=False): post_data = {"torrents_dir": dirname, "recursive": int(recursive)} request_mgr = TriblerRequestManager() request_mgr.perform_request("mychannel/torrents", self.on_torrent_to_channel_added, method='PUT', data=post_data) def add_torrent_url_to_channel(self, url): post_data = {"uri": url} request_mgr = TriblerRequestManager() request_mgr.perform_request("mychannel/torrents", self.on_torrent_to_channel_added, method='PUT', data=post_data) def on_torrent_to_channel_added(self, result): if not result: return if 'added' in result: self.load_my_torrents()
class SettingsPage(QWidget): """ This class is responsible for displaying and adjusting the settings present in Tribler. """ def __init__(self): QWidget.__init__(self) self.settings = None self.settings_request_mgr = None self.saved_dialog = None def initialize_settings_page(self): self.window().settings_tab.initialize() self.window().settings_tab.clicked_tab_button.connect( self.clicked_tab_button) self.window().settings_save_button.clicked.connect(self.save_settings) self.window().developer_mode_enabled_checkbox.stateChanged.connect( self.on_developer_mode_checkbox_changed) self.window().download_settings_anon_checkbox.stateChanged.connect( self.on_anon_download_state_changed) def on_developer_mode_checkbox_changed(self, _): self.window().gui_settings.setValue( "debug", self.window().developer_mode_enabled_checkbox.isChecked()) self.window().left_menu_button_debug.setHidden( not self.window().developer_mode_enabled_checkbox.isChecked()) def on_anon_download_state_changed(self, _): if self.window().download_settings_anon_checkbox.isChecked(): self.window().download_settings_anon_seeding_checkbox.setChecked( True) self.window().download_settings_anon_seeding_checkbox.setEnabled( not self.window().download_settings_anon_checkbox.isChecked()) def initialize_with_settings(self, settings): self.settings = settings settings = settings["settings"] gui_settings = self.window().gui_settings # General settings self.window().developer_mode_enabled_checkbox.setChecked( get_gui_setting(gui_settings, "debug", False, is_bool=True)) self.window().family_filter_checkbox.setChecked( settings['general']['family_filter']) self.window().download_location_input.setText( settings['downloadconfig']['saveas']) self.window().always_ask_location_checkbox.setChecked( get_gui_setting(gui_settings, "ask_download_settings", True, is_bool=True)) self.window().download_settings_anon_checkbox.setChecked( get_gui_setting(gui_settings, "default_anonymity_enabled", True, is_bool=True)) self.window().download_settings_anon_seeding_checkbox.setChecked( get_gui_setting(gui_settings, "default_safeseeding_enabled", True, is_bool=True)) self.window().watchfolder_enabled_checkbox.setChecked( settings['watch_folder']['enabled']) self.window().watchfolder_location_input.setText( settings['watch_folder']['watch_folder_dir']) # Connection settings self.window().firewall_current_port_input.setText( str(settings['general']['minport'])) self.window().lt_proxy_type_combobox.setCurrentIndex( settings['libtorrent']['lt_proxytype']) if settings['libtorrent']['lt_proxyserver']: self.window().lt_proxy_server_input.setText( settings['libtorrent']['lt_proxyserver'][0]) self.window().lt_proxy_port_input.setText( settings['libtorrent']['lt_proxyserver'][1]) if settings['libtorrent']['lt_proxyauth']: self.window().lt_proxy_username_input.setText( settings['libtorrent']['lt_proxyauth'][0]) self.window().lt_proxy_password_input.setText( settings['libtorrent']['lt_proxyauth'][1]) self.window().lt_utp_checkbox.setChecked(settings['libtorrent']['utp']) max_conn_download = settings['libtorrent']['max_connections_download'] if max_conn_download == -1: max_conn_download = 0 self.window().max_connections_download_input.setText( str(max_conn_download)) # Bandwidth settings self.window().upload_rate_limit_input.setText( str(settings['Tribler']['maxuploadrate'])) self.window().download_rate_limit_input.setText( str(settings['Tribler']['maxdownloadrate'])) # Seeding settings getattr( self.window(), "seeding_" + settings['downloadconfig']['seeding_mode'] + "_radio").setChecked(True) self.window().seeding_time_input.setText( seconds_to_string(settings['downloadconfig']['seeding_time'])) ind = self.window().seeding_ratio_combobox.findText( str(settings['downloadconfig']['seeding_ratio'])) if ind != -1: self.window().seeding_ratio_combobox.setCurrentIndex(ind) # Anonymity settings self.window().allow_exit_node_checkbox.setChecked( settings['tunnel_community']['exitnode_enabled']) self.window().number_hops_slider.setValue( int(settings['Tribler']['default_number_hops']) - 1) self.window().multichain_enabled_checkbox.setChecked( settings['multichain']['enabled']) def load_settings(self): self.settings_request_mgr = TriblerRequestManager() self.settings_request_mgr.perform_request( "settings", self.initialize_with_settings) def clicked_tab_button(self, tab_button_name): if tab_button_name == "settings_general_button": self.window().settings_stacked_widget.setCurrentIndex( PAGE_SETTINGS_GENERAL) elif tab_button_name == "settings_connection_button": self.window().settings_stacked_widget.setCurrentIndex( PAGE_SETTINGS_CONNECTION) elif tab_button_name == "settings_bandwidth_button": self.window().settings_stacked_widget.setCurrentIndex( PAGE_SETTINGS_BANDWIDTH) elif tab_button_name == "settings_seeding_button": self.window().settings_stacked_widget.setCurrentIndex( PAGE_SETTINGS_SEEDING) elif tab_button_name == "settings_anonymity_button": self.window().settings_stacked_widget.setCurrentIndex( PAGE_SETTINGS_ANONYMITY) def save_settings(self): # Create a dictionary with all available settings settings_data = { 'general': {}, 'Tribler': {}, 'downloadconfig': {}, 'libtorrent': {}, 'watch_folder': {}, 'tunnel_community': {}, 'multichain': {} } settings_data['general']['family_filter'] = self.window( ).family_filter_checkbox.isChecked() settings_data['downloadconfig']['saveas'] = self.window( ).download_location_input.text() settings_data['watch_folder']['enabled'] = self.window( ).watchfolder_enabled_checkbox.isChecked() if settings_data['watch_folder']['enabled']: settings_data['watch_folder']['watch_folder_dir'] = self.window( ).watchfolder_location_input.text() settings_data['general']['minport'] = self.window( ).firewall_current_port_input.text() settings_data['libtorrent']['lt_proxytype'] = self.window( ).lt_proxy_type_combobox.currentIndex() if len(self.window().lt_proxy_server_input.text()) > 0 and len( self.window().lt_proxy_port_input.text()) > 0: settings_data['libtorrent']['lt_proxyserver'] = [None, None] settings_data['libtorrent']['lt_proxyserver'][0] = self.window( ).lt_proxy_server_input.text() settings_data['libtorrent']['lt_proxyserver'][1] = self.window( ).lt_proxy_port_input.text() if len(self.window().lt_proxy_username_input.text()) > 0 and \ len(self.window().lt_proxy_password_input.text()) > 0: settings_data['libtorrent']['lt_proxyauth'] = [None, None] settings_data['libtorrent']['lt_proxyauth'][0] = self.window( ).lt_proxy_username_input.text() settings_data['libtorrent']['lt_proxyauth'][1] = self.window( ).lt_proxy_password_input.text() settings_data['libtorrent']['utp'] = self.window( ).lt_utp_checkbox.isChecked() try: max_conn_download = int( self.window().max_connections_download_input.text()) except ValueError: ConfirmationDialog.show_error( self.window(), "Invalid number of connections", "You've entered an invalid format for the maximum number of connections." ) return if max_conn_download == 0: max_conn_download = -1 settings_data['libtorrent'][ 'max_connections_download'] = max_conn_download if self.window().upload_rate_limit_input.text(): settings_data['Tribler']['maxuploadrate'] = self.window( ).upload_rate_limit_input.text() if self.window().download_rate_limit_input.text(): settings_data['Tribler']['maxdownloadrate'] = self.window( ).download_rate_limit_input.text() seeding_modes = ['forever', 'time', 'never', 'ratio'] selected_mode = 'forever' for seeding_mode in seeding_modes: if getattr(self.window(), "seeding_" + seeding_mode + "_radio").isChecked(): selected_mode = seeding_mode break settings_data['downloadconfig']['seeding_mode'] = selected_mode settings_data['downloadconfig']['seeding_ratio'] = self.window( ).seeding_ratio_combobox.currentText() try: settings_data['downloadconfig'][ 'seeding_time'] = string_to_minutes( self.window().seeding_time_input.text()) except ValueError: ConfirmationDialog.show_error( self.window(), "Invalid seeding time", "You've entered an invalid format for the seeding time (expected HH:MM)" ) return settings_data['tunnel_community']['exitnode_enabled'] = self.window( ).allow_exit_node_checkbox.isChecked() settings_data['Tribler']['default_number_hops'] = self.window( ).number_hops_slider.value() + 1 settings_data['multichain']['enabled'] = self.window( ).multichain_enabled_checkbox.isChecked() self.settings_request_mgr = TriblerRequestManager() self.settings_request_mgr.perform_request( "settings", self.on_settings_saved, method='POST', data=json.dumps(settings_data)) def on_settings_saved(self, _): # Now save the GUI settings self.window().gui_settings.setValue( "ask_download_settings", self.window().always_ask_location_checkbox.isChecked()) self.window().gui_settings.setValue( "default_anonymity_enabled", self.window().download_settings_anon_checkbox.isChecked()) self.window().gui_settings.setValue( "default_safeseeding_enabled", self.window().download_settings_anon_seeding_checkbox.isChecked()) self.saved_dialog = ConfirmationDialog( TriblerRequestManager.window, "Settings saved", "Your settings have been saved.", [('close', BUTTON_TYPE_NORMAL)]) self.saved_dialog.button_clicked.connect(self.on_dialog_cancel_clicked) self.saved_dialog.show() self.window().fetch_settings() def on_dialog_cancel_clicked(self, _): self.saved_dialog.setParent(None) self.saved_dialog = None
class EditChannelPage(QWidget): """ This class is responsible for managing lists and data on your channel page """ on_torrents_removed = pyqtSignal(list) on_all_torrents_removed = pyqtSignal() on_commit = pyqtSignal() def __init__(self): QWidget.__init__(self) self.channel_overview = None self.chosen_dir = None self.dialog = None self.editchannel_request_mgr = None self.model = None self.controller = None self.channel_dirty = False self.gui_settings = None self.commit_timer = None self.autocommit_enabled = None def initialize_edit_channel_page(self, gui_settings): self.gui_settings = gui_settings self.window().create_channel_intro_button.clicked.connect(self.on_create_channel_intro_button_clicked) self.window().create_channel_form.hide() self.update_channel_commit_views() self.window().edit_channel_stacked_widget.setCurrentIndex(1) self.window().edit_channel_details_stacked_widget.setCurrentIndex(PAGE_EDIT_CHANNEL_OVERVIEW) self.window().create_channel_button.clicked.connect(self.on_create_channel_button_pressed) self.window().edit_channel_save_button.clicked.connect(self.on_edit_channel_save_button_pressed) self.window().edit_channel_commit_button.clicked.connect(self.clicked_edit_channel_commit_button) # Tab bar buttons self.window().channel_settings_tab.initialize() self.window().channel_settings_tab.clicked_tab_button.connect(self.clicked_tab_button) self.window().export_channel_button.clicked.connect(self.on_export_mdblob) # TODO: re-enable remove_selected button self.window().remove_selected_button.setHidden(True) # Connect torrent addition/removal buttons self.window().remove_selected_button.clicked.connect(self.on_torrents_remove_selected_clicked) self.window().remove_all_button.clicked.connect(self.on_torrents_remove_all_clicked) self.window().add_button.clicked.connect(self.on_torrents_add_clicked) self.model = MyTorrentsContentModel() self.controller = MyTorrentsTableViewController(self.model, self.window().edit_channel_torrents_container.content_table, self.window().edit_channel_torrents_container.details_container, self.window().edit_channel_torrents_num_items_label, self.window().edit_channel_torrents_filter) self.window().edit_channel_torrents_container.details_container.hide() self.autocommit_enabled = get_gui_setting(self.gui_settings, "autocommit_enabled", True, is_bool=True) if self.gui_settings else True # Commit the channel just in case there are uncommitted changes left since the last time (e.g. Tribler crashed) # The timer thing here is a workaround for race condition with the core startup if self.autocommit_enabled: if not self.commit_timer: self.commit_timer = QTimer() self.commit_timer.setSingleShot(True) self.commit_timer.timeout.connect(self.autocommit_fired) self.controller.table_view.setColumnHidden(3, True) self.model.exclude_deleted = True self.commit_timer.stop() self.commit_timer.start(10000) else: self.controller.table_view.setColumnHidden(4, True) self.model.exclude_deleted = False def update_channel_commit_views(self, deleted_index=None): if self.channel_dirty and self.autocommit_enabled: self.commit_timer.stop() self.commit_timer.start(CHANNEL_COMMIT_DELAY) if deleted_index: # TODO: instead of reloading the whole table, just remove the deleted row and update start and end self.load_my_torrents() self.window().commit_control_bar.setHidden(not self.channel_dirty or self.autocommit_enabled) def load_my_channel_overview(self): if not self.channel_overview: self.window().edit_channel_stacked_widget.setCurrentIndex(2) self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request("mychannel", self.initialize_with_channel_overview, capture_errors=False) def initialize_with_channel_overview(self, overview): if not overview: return if 'error' in overview: self.window().edit_channel_stacked_widget.setCurrentIndex(0) return self.channel_overview = overview["mychannel"] self.channel_dirty = self.channel_overview['dirty'] self.update_channel_commit_views() self.window().export_channel_button.setHidden(False) self.window().edit_channel_name_label.setText("My channel") self.window().edit_channel_overview_name_label.setText(self.channel_overview["name"]) self.window().edit_channel_description_label.setText(self.channel_overview["description"]) self.window().edit_channel_identifier_label.setText(self.channel_overview["public_key"]) self.window().edit_channel_name_edit.setText(self.channel_overview["name"]) self.window().edit_channel_description_edit.setText(self.channel_overview["description"]) self.window().edit_channel_stacked_widget.setCurrentIndex(1) self.model.channel_pk = self.channel_overview["public_key"] def on_create_channel_button_pressed(self): channel_name = self.window().new_channel_name_edit.text() channel_description = self.window().new_channel_description_edit.toPlainText() if len(channel_name) == 0: self.window().new_channel_name_label.setStyleSheet("color: red;") return post_data = { "name": channel_name, "description": channel_description } self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request("mychannel", self.on_channel_created, data=post_data, method='PUT') def on_channel_created(self, result): if not result: return if u'added' in result: self.window().create_channel_button.setEnabled(True) self.load_my_channel_overview() def on_edit_channel_save_button_pressed(self): channel_name = self.window().edit_channel_name_edit.text() channel_description = self.window().edit_channel_description_edit.toPlainText() post_data = { "name": channel_name, "description": channel_description } self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request("mychannel", self.on_channel_edited, data=post_data, method='POST') def on_channel_edited(self, result): if not result: return if 'modified' in result: self.window().edit_channel_name_label.setText(self.window().edit_channel_name_edit.text()) self.window().edit_channel_description_label.setText( self.window().edit_channel_description_edit.toPlainText()) def clicked_tab_button(self, tab_button_name): if tab_button_name == "edit_channel_overview_button": self.window().edit_channel_details_stacked_widget.setCurrentIndex(PAGE_EDIT_CHANNEL_OVERVIEW) elif tab_button_name == "edit_channel_settings_button": self.window().edit_channel_details_stacked_widget.setCurrentIndex(PAGE_EDIT_CHANNEL_SETTINGS) elif tab_button_name == "edit_channel_torrents_button": self.load_my_torrents() self.window().edit_channel_details_stacked_widget.setCurrentIndex(PAGE_EDIT_CHANNEL_TORRENTS) def load_my_torrents(self): self.controller.model.reset() self.controller.perform_query(first=1, last=50) # Load the first 50 torrents def on_create_channel_intro_button_clicked(self): self.window().create_channel_form.show() self.window().create_channel_intro_button_container.hide() self.window().create_new_channel_intro_label.setText("Please enter your channel details below.") def on_export_mdblob(self): export_dir = QFileDialog.getExistingDirectory(self, "Please select the destination directory", "", QFileDialog.ShowDirsOnly) if len(export_dir) == 0: return # Show confirmation dialog where we specify the name of the file mdblob_name = self.channel_overview["public_key"] dialog = ConfirmationDialog(self, "Export mdblob file", "Please enter the name of the channel metadata file:", [('SAVE', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)], show_input=True) 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_request_done(dest_path, data): 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) dialog.dialog_widget.dialog_input.setPlaceholderText('Channel file name') dialog.dialog_widget.dialog_input.setText("%s.mdblob" % mdblob_name) dialog.dialog_widget.dialog_input.setFocus() dialog.button_clicked.connect(on_export_download_dialog_done) dialog.show() # Torrent removal-related methods def on_torrents_remove_selected_clicked(self): selected_items = self.controller.table_view.selectedIndexes() num_selected = len(selected_items) if num_selected == 0: return selected_infohashes = [self.model.data_items[row][u'infohash'] for row in set([index.row() for index in selected_items])] self.dialog = ConfirmationDialog(self, "Remove %s selected torrents" % len(selected_infohashes), "Are you sure that you want to remove %s selected torrents " "from your channel?" % len(selected_infohashes), [('CONFIRM', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)]) self.dialog.button_clicked.connect(lambda action: self.on_torrents_remove_selected_action(action, selected_infohashes)) self.dialog.show() def on_torrents_remove_selected_action(self, action, items): if action == 0: items = [str(item) for item in items] infohashes = ",".join(items) post_data = { "infohashes": infohashes, "status": COMMIT_STATUS_TODELETE } request_mgr = TriblerRequestManager() request_mgr.perform_request("mychannel/torrents", lambda response: self.on_torrents_removed_response(response, items), data=post_data, method='POST') if self.dialog: self.dialog.close_dialog() self.dialog = None def on_torrents_removed_response(self, json_result, infohashes): if not json_result: return if 'success' in json_result and json_result['success']: self.on_torrents_removed.emit(infohashes) self.load_my_torrents() def on_torrents_remove_all_clicked(self): self.dialog = ConfirmationDialog(self.window(), "Remove all torrents", "Are you sure that you want to remove all torrents from your channel?", [('CONFIRM', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)]) self.dialog.button_clicked.connect(self.on_torrents_remove_all_action) self.dialog.show() def on_torrents_remove_all_action(self, action): if action == 0: request_mgr = TriblerRequestManager() request_mgr.perform_request("mychannel/torrents", self.on_all_torrents_removed_response, method='DELETE') self.dialog.close_dialog() self.dialog = None def on_all_torrents_removed_response(self, json_result): if not json_result: return if 'success' in json_result and json_result['success']: self.on_all_torrents_removed.emit() self.load_my_torrents() # Torrent addition-related methods def on_add_torrents_browse_dir(self): chosen_dir = QFileDialog.getExistingDirectory(self, "Please select the directory containing the .torrent files", QDir.homePath(), QFileDialog.ShowDirsOnly) if not chosen_dir: return self.chosen_dir = chosen_dir self.dialog = ConfirmationDialog(self, "Add torrents from directory", "Add all torrent files from the following directory " "to your Tribler channel:\n\n%s" % chosen_dir, [('ADD', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)], checkbox_text="Include subdirectories (recursive mode)") self.dialog.button_clicked.connect(self.on_confirm_add_directory_dialog) self.dialog.show() def on_confirm_add_directory_dialog(self, action): if action == 0: self.add_dir_to_channel(self.chosen_dir, recursive=self.dialog.checkbox.isChecked()) if self.dialog: self.dialog.close_dialog() self.dialog = None self.chosen_dir = None def on_torrents_add_clicked(self): menu = TriblerActionMenu(self) browse_files_action = QAction('Import torrent from file', self) browse_dir_action = QAction('Import torrent(s) from dir', self) add_url_action = QAction('Add torrent from URL/magnet link', self) create_torrent_action = QAction('Create torrent from file(s)', self) browse_files_action.triggered.connect(self.on_add_torrent_browse_file) browse_dir_action.triggered.connect(self.on_add_torrents_browse_dir) add_url_action.triggered.connect(self.on_add_torrent_from_url) create_torrent_action.triggered.connect(self.on_create_torrent_from_files) menu.addAction(browse_files_action) menu.addAction(browse_dir_action) menu.addAction(add_url_action) menu.addAction(create_torrent_action) menu.exec_(QCursor.pos()) def on_create_torrent_from_files(self): self.window().edit_channel_details_create_torrent.initialize() self.window().edit_channel_details_stacked_widget.setCurrentIndex(PAGE_EDIT_CHANNEL_CREATE_TORRENT) def on_add_torrent_browse_file(self): filenames = QFileDialog.getOpenFileNames( self, "Please select the .torrent file", "", "Torrent files (*.torrent)") if not filenames[0]: return for filename in filenames[0]: self.add_torrent_to_channel(filename) def on_add_torrent_from_url(self): self.dialog = ConfirmationDialog(self, "Add torrent from URL/magnet link", "Please enter the URL/magnet link in the field below:", [('ADD', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)], show_input=True) self.dialog.dialog_widget.dialog_input.setPlaceholderText('URL/magnet link') self.dialog.button_clicked.connect(self.on_torrent_from_url_dialog_done) self.dialog.show() def on_torrent_from_url_dialog_done(self, action): if action == 0: self.add_torrent_url_to_channel(self.dialog.dialog_widget.dialog_input.text()) self.dialog.close_dialog() self.dialog = None def autocommit_fired(self): def commit_channel(overview): try: if overview and overview['mychannel']['dirty']: self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request("mychannel/commit", lambda _: None, method='POST', capture_errors=False) except KeyError: return if self.channel_overview: self.clicked_edit_channel_commit_button() else: self.editchannel_request_mgr = TriblerRequestManager() self.editchannel_request_mgr.perform_request("mychannel", commit_channel, capture_errors=False) # Commit button-related methods def clicked_edit_channel_commit_button(self): request_mgr = TriblerRequestManager() request_mgr.perform_request("mychannel/commit", self.on_channel_committed, method='POST') def on_channel_committed(self, result): if not result: return if 'success' in result and result['success']: self.channel_dirty = False self.update_channel_commit_views() self.on_commit.emit() if not self.autocommit_enabled: self.load_my_torrents() def add_torrent_to_channel(self, filename): with open(filename, "rb") as torrent_file: torrent_content = b64encode(torrent_file.read()) request_mgr = TriblerRequestManager() request_mgr.perform_request("mychannel/torrents", self.on_torrent_to_channel_added, method='PUT', data={"torrent": torrent_content}) def add_dir_to_channel(self, dirname, recursive=False): post_data = { "torrents_dir": dirname, "recursive": int(recursive) } request_mgr = TriblerRequestManager() request_mgr.perform_request("mychannel/torrents", self.on_torrent_to_channel_added, method='PUT', data=post_data) def add_torrent_url_to_channel(self, url): post_data = {"uri": url} request_mgr = TriblerRequestManager() request_mgr.perform_request("mychannel/torrents", self.on_torrent_to_channel_added, method='PUT', data=post_data) def on_torrent_to_channel_added(self, result): if not result: return if 'added' in result: self.load_my_torrents()
class TriblerWindow(QMainWindow): resize_event = pyqtSignal() escape_pressed = pyqtSignal() received_search_completions = pyqtSignal(object) def on_exception(self, *exc_info): # Stop the download loop self.downloads_page.stop_loading_downloads() # Add info about whether we are stopping Tribler or not os.environ['TRIBLER_SHUTTING_DOWN'] = str( self.core_manager.shutting_down) if not self.core_manager.shutting_down: self.core_manager.stop(stop_app_on_shutdown=False) self.setHidden(True) if self.debug_window: self.debug_window.setHidden(True) exception_text = "".join(traceback.format_exception(*exc_info)) logging.error(exception_text) if not self.feedback_dialog_is_open: dialog = FeedbackDialog( self, exception_text, self.core_manager.events_manager.tribler_version, self.start_time) self.feedback_dialog_is_open = True _ = dialog.exec_() def __init__(self): QMainWindow.__init__(self) self.navigation_stack = [] self.feedback_dialog_is_open = False self.tribler_started = False self.tribler_settings = None self.debug_window = None self.core_manager = CoreManager() self.pending_requests = {} self.pending_uri_requests = [] self.download_uri = None self.dialog = None self.start_download_dialog_active = False self.request_mgr = None self.search_request_mgr = None self.search_suggestion_mgr = None self.selected_torrent_files = [] self.vlc_available = True self.has_search_results = False self.start_time = time.time() sys.excepthook = self.on_exception uic.loadUi(get_ui_file_path('mainwindow.ui'), self) TriblerRequestManager.window = self self.tribler_status_bar.hide() self.magnet_handler = MagnetHandler(self.window) QDesktopServices.setUrlHandler("magnet", self.magnet_handler, "on_open_magnet_link") QCoreApplication.setOrganizationDomain("nl") QCoreApplication.setOrganizationName("TUDelft") QCoreApplication.setApplicationName("Tribler") QCoreApplication.setAttribute(Qt.AA_UseHighDpiPixmaps) self.read_settings() # Remove the focus rect on OS X for widget in self.findChildren(QLineEdit) + self.findChildren( QListWidget) + self.findChildren(QTreeWidget): widget.setAttribute(Qt.WA_MacShowFocusRect, 0) self.menu_buttons = [ self.left_menu_button_home, self.left_menu_button_search, self.left_menu_button_my_channel, self.left_menu_button_subscriptions, self.left_menu_button_video_player, self.left_menu_button_downloads, self.left_menu_button_discovered ] self.video_player_page.initialize_player() self.search_results_page.initialize_search_results_page() self.settings_page.initialize_settings_page() self.subscribed_channels_page.initialize() self.edit_channel_page.initialize_edit_channel_page() self.downloads_page.initialize_downloads_page() self.home_page.initialize_home_page() self.loading_page.initialize_loading_page() self.discovering_page.initialize_discovering_page() self.discovered_page.initialize_discovered_page() self.trust_page.initialize_trust_page() self.stackedWidget.setCurrentIndex(PAGE_LOADING) # Create the system tray icon if QSystemTrayIcon.isSystemTrayAvailable(): self.tray_icon = QSystemTrayIcon() use_monochrome_icon = get_gui_setting(self.gui_settings, "use_monochrome_icon", False, is_bool=True) self.update_tray_icon(use_monochrome_icon) self.hide_left_menu_playlist() self.left_menu_button_debug.setHidden(True) self.top_menu_button.setHidden(True) self.left_menu.setHidden(True) self.trust_button.setHidden(True) self.settings_button.setHidden(True) self.add_torrent_button.setHidden(True) self.top_search_bar.setHidden(True) # Set various icons self.top_menu_button.setIcon(QIcon(get_image_path('menu.png'))) self.search_completion_model = QStringListModel() completer = QCompleter() completer.setModel(self.search_completion_model) completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion) self.item_delegate = QStyledItemDelegate() completer.popup().setItemDelegate(self.item_delegate) completer.popup().setStyleSheet(""" QListView { background-color: #404040; } QListView::item { color: #D0D0D0; padding-top: 5px; padding-bottom: 5px; } QListView::item:hover { background-color: #707070; } """) self.top_search_bar.setCompleter(completer) # Toggle debug if developer mode is enabled self.window().left_menu_button_debug.setHidden(not get_gui_setting( self.gui_settings, "debug", False, is_bool=True)) self.core_manager.start() self.core_manager.events_manager.received_search_result_channel.connect( self.search_results_page.received_search_result_channel) self.core_manager.events_manager.received_search_result_torrent.connect( self.search_results_page.received_search_result_torrent) self.core_manager.events_manager.torrent_finished.connect( self.on_torrent_finished) self.core_manager.events_manager.new_version_available.connect( self.on_new_version_available) self.core_manager.events_manager.tribler_started.connect( self.on_tribler_started) # Install signal handler for ctrl+c events def sigint_handler(*_): self.close_tribler() signal.signal(signal.SIGINT, sigint_handler) self.installEventFilter(self.video_player_page) self.show() def update_tray_icon(self, use_monochrome_icon): if not QSystemTrayIcon.isSystemTrayAvailable(): return if use_monochrome_icon: self.tray_icon.setIcon( QIcon(QPixmap(get_image_path('monochrome_tribler.png')))) else: self.tray_icon.setIcon( QIcon(QPixmap(get_image_path('tribler.png')))) self.tray_icon.show() def on_torrent_finished(self, torrent_info): if QSystemTrayIcon.isSystemTrayAvailable(): self.window().tray_icon.showMessage( "Download finished", "Download of %s has finished." % torrent_info["name"]) def show_loading_screen(self): self.top_menu_button.setHidden(True) self.left_menu.setHidden(True) self.trust_button.setHidden(True) self.settings_button.setHidden(True) self.add_torrent_button.setHidden(True) self.top_search_bar.setHidden(True) self.stackedWidget.setCurrentIndex(PAGE_LOADING) def on_tribler_started(self): self.tribler_started = True self.top_menu_button.setHidden(False) self.left_menu.setHidden(False) self.trust_button.setHidden(False) self.settings_button.setHidden(False) self.add_torrent_button.setHidden(False) self.top_search_bar.setHidden(False) # fetch the settings, needed for the video player port self.request_mgr = TriblerRequestManager() self.fetch_settings() self.downloads_page.start_loading_downloads() self.home_page.load_popular_torrents() if not self.gui_settings.value( "first_discover", False) and not self.core_manager.use_existing_core: self.window().gui_settings.setValue("first_discover", True) self.discovering_page.is_discovering = True self.stackedWidget.setCurrentIndex(PAGE_DISCOVERING) else: self.clicked_menu_button_home() def show_status_bar(self, message): self.tribler_status_bar_label.setText(message) self.tribler_status_bar.show() def hide_status_bar(self): self.tribler_status_bar.hide() def process_uri_request(self): """ Process a URI request if we have one in the queue. """ if len(self.pending_uri_requests) == 0: return uri = self.pending_uri_requests.pop() if uri.startswith('file') or uri.startswith('magnet'): self.start_download_from_uri(uri) def perform_start_download_request(self, uri, anon_download, safe_seeding, destination, selected_files, total_files=0, callback=None): selected_files_uri = "" if len(selected_files) != total_files: # Not all files included selected_files_uri = u'&' + u''.join( u"selected_files[]=%s&" % file for file in selected_files)[:-1] anon_hops = int(self.tribler_settings['download_defaults'] ['number_hops']) if anon_download else 0 safe_seeding = 1 if safe_seeding else 0 post_data = "uri=%s&anon_hops=%d&safe_seeding=%d&destination=%s%s" % ( uri, anon_hops, safe_seeding, destination, selected_files_uri) post_data = post_data.encode( 'utf-8') # We need to send bytes in the request, not unicode request_mgr = TriblerRequestManager() self.pending_requests[request_mgr.request_id] = request_mgr request_mgr.perform_request( "downloads", callback if callback else self.on_download_added, method='PUT', data=post_data) # Save the download location to the GUI settings current_settings = get_gui_setting(self.gui_settings, "recent_download_locations", "") recent_locations = current_settings.split( ",") if len(current_settings) > 0 else [] encoded_destination = destination.encode('hex') if encoded_destination in recent_locations: recent_locations.remove(encoded_destination) recent_locations.insert(0, encoded_destination) if len(recent_locations) > 5: recent_locations = recent_locations[:5] self.gui_settings.setValue("recent_download_locations", ','.join(recent_locations)) def on_new_version_available(self, version): if version == str(self.gui_settings.value('last_reported_version')): return self.dialog = ConfirmationDialog( self, "New version available", "Version %s of Tribler is available.Do you want to visit the website to " "download the newest version?" % version, [('IGNORE', BUTTON_TYPE_NORMAL), ('LATER', BUTTON_TYPE_NORMAL), ('OK', BUTTON_TYPE_NORMAL)]) self.dialog.button_clicked.connect( lambda action: self.on_new_version_dialog_done(version, action)) self.dialog.show() def on_new_version_dialog_done(self, version, action): if action == 0: # ignore self.gui_settings.setValue("last_reported_version", version) elif action == 2: # ok import webbrowser webbrowser.open("https://tribler.org") self.dialog.setParent(None) self.dialog = None def read_settings(self): self.gui_settings = QSettings() center = QApplication.desktop().availableGeometry(self).center() pos = self.gui_settings.value( "pos", QPoint(center.x() - self.width() * 0.5, center.y() - self.height() * 0.5)) size = self.gui_settings.value("size", self.size()) self.move(pos) self.resize(size) def on_search_text_change(self, text): self.search_suggestion_mgr = TriblerRequestManager() self.search_suggestion_mgr.perform_request( "search/completions?q=%s" % text, self.on_received_search_completions) def on_received_search_completions(self, completions): self.received_search_completions.emit(completions) self.search_completion_model.setStringList(completions["completions"]) def fetch_settings(self): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("settings", self.received_settings, capture_errors=False) def received_settings(self, settings): # If we cannot receive the settings, stop Tribler with an option to send the crash report. if 'error' in settings: raise RuntimeError( TriblerRequestManager.get_message_from_error(settings)) else: self.tribler_settings = settings['settings'] # Disable various components based on the settings if not self.tribler_settings['search_community']['enabled']: self.window().top_search_bar.setHidden(True) if not self.tribler_settings['video_server']['enabled']: self.left_menu_button_video_player.setHidden(True) # Set the video server port self.video_player_page.video_player_port = self.tribler_settings[ "video_server"]["port"] # process pending file requests (i.e. someone clicked a torrent file when Tribler was closed) # We do this after receiving the settings so we have the default download location. self.process_uri_request() def on_top_search_button_click(self): self.left_menu_button_search.setChecked(True) self.has_search_results = True self.clicked_menu_button_search() self.search_results_page.perform_search(self.top_search_bar.text()) self.search_request_mgr = TriblerRequestManager() self.search_request_mgr.perform_request( "search?q=%s" % self.top_search_bar.text(), None) def on_settings_button_click(self): self.deselect_all_menu_buttons() self.stackedWidget.setCurrentIndex(PAGE_SETTINGS) self.settings_page.load_settings() self.navigation_stack = [] self.hide_left_menu_playlist() def on_trust_button_click(self): self.deselect_all_menu_buttons() self.stackedWidget.setCurrentIndex(PAGE_TRUST) self.trust_page.load_trust_statistics() self.navigation_stack = [] self.hide_left_menu_playlist() def on_add_torrent_button_click(self, pos): menu = TriblerActionMenu(self) browse_files_action = QAction('Import torrent from file', self) browse_directory_action = QAction('Import torrents from directory', self) add_url_action = QAction('Import torrent from magnet/URL', self) browse_files_action.triggered.connect(self.on_add_torrent_browse_file) browse_directory_action.triggered.connect( self.on_add_torrent_browse_dir) add_url_action.triggered.connect(self.on_add_torrent_from_url) menu.addAction(browse_files_action) menu.addAction(browse_directory_action) menu.addAction(add_url_action) menu.exec_(self.mapToGlobal(self.add_torrent_button.pos())) def on_add_torrent_browse_file(self): filenames = QFileDialog.getOpenFileNames( self, "Please select the .torrent file", "", "Torrent files (*.torrent)") if len(filenames[0]) > 0: [ self.pending_uri_requests.append(u"file:%s" % filename) for filename in filenames[0] ] self.process_uri_request() def start_download_from_uri(self, uri): self.download_uri = uri if get_gui_setting(self.gui_settings, "ask_download_settings", True, is_bool=True): self.dialog = StartDownloadDialog(self.window().stackedWidget, self.download_uri) self.dialog.button_clicked.connect(self.on_start_download_action) self.dialog.show() self.start_download_dialog_active = True else: 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'], [], 0) self.process_uri_request() def on_start_download_action(self, action): if action == 1: 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()) self.dialog.request_mgr.cancel_request( ) # To abort the torrent info request self.dialog.setParent(None) 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_add_torrent_browse_dir(self): chosen_dir = QFileDialog.getExistingDirectory( self, "Please select the directory containing the .torrent files", "", QFileDialog.ShowDirsOnly) if len(chosen_dir) != 0: self.selected_torrent_files = [ torrent_file for torrent_file in glob.glob(chosen_dir + "/*.torrent") ] self.dialog = ConfirmationDialog( self, "Add torrents from directory", "Are you sure you want to add %d torrents to Tribler?" % len(self.selected_torrent_files), [('ADD', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)]) self.dialog.button_clicked.connect( self.on_confirm_add_directory_dialog) self.dialog.show() def on_confirm_add_directory_dialog(self, action): if action == 0: for torrent_file in self.selected_torrent_files: escaped_uri = quote_plus( (u"file:%s" % torrent_file).encode('utf-8')) self.perform_start_download_request( escaped_uri, self.window().tribler_settings['download_defaults'] ['anonymity_enabled'], self.window().tribler_settings['download_defaults'] ['safeseeding_enabled'], self.tribler_settings['download_defaults']['saveas'], [], 0) self.dialog.setParent(None) self.dialog = None def on_add_torrent_from_url(self): self.dialog = ConfirmationDialog( self, "Add torrent from URL/magnet link", "Please enter the URL/magnet link in the field below:", [('ADD', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)], show_input=True) self.dialog.dialog_widget.dialog_input.setPlaceholderText( 'URL/magnet link') self.dialog.dialog_widget.dialog_input.setFocus() self.dialog.button_clicked.connect( self.on_torrent_from_url_dialog_done) self.dialog.show() def on_torrent_from_url_dialog_done(self, action): uri = self.dialog.dialog_widget.dialog_input.text() # Remove first dialog self.dialog.setParent(None) self.dialog = None if action == 0: self.start_download_from_uri(uri) def on_download_added(self, result): if len(self.pending_uri_requests ) == 0: # Otherwise, we first process the remaining requests. self.window().left_menu_button_downloads.click() else: self.process_uri_request() def on_top_menu_button_click(self): if self.left_menu.isHidden(): self.left_menu.show() else: self.left_menu.hide() def deselect_all_menu_buttons(self, except_select=None): for button in self.menu_buttons: if button == except_select: button.setEnabled(False) continue button.setEnabled(True) if button == self.left_menu_button_search and not self.has_search_results: button.setEnabled(False) button.setChecked(False) def clicked_menu_button_home(self): self.deselect_all_menu_buttons(self.left_menu_button_home) self.stackedWidget.setCurrentIndex(PAGE_HOME) self.navigation_stack = [] self.hide_left_menu_playlist() def clicked_menu_button_search(self): self.deselect_all_menu_buttons(self.left_menu_button_search) self.stackedWidget.setCurrentIndex(PAGE_SEARCH_RESULTS) self.navigation_stack = [] self.hide_left_menu_playlist() def clicked_menu_button_discovered(self): self.deselect_all_menu_buttons(self.left_menu_button_discovered) self.stackedWidget.setCurrentIndex(PAGE_DISCOVERED) self.discovered_page.load_discovered_channels() self.navigation_stack = [] self.hide_left_menu_playlist() def clicked_menu_button_my_channel(self): self.deselect_all_menu_buttons(self.left_menu_button_my_channel) self.stackedWidget.setCurrentIndex(PAGE_EDIT_CHANNEL) self.edit_channel_page.load_my_channel_overview() self.navigation_stack = [] self.hide_left_menu_playlist() def clicked_menu_button_video_player(self): self.deselect_all_menu_buttons(self.left_menu_button_video_player) self.stackedWidget.setCurrentIndex(PAGE_VIDEO_PLAYER) self.navigation_stack = [] self.show_left_menu_playlist() def clicked_menu_button_downloads(self): self.deselect_all_menu_buttons(self.left_menu_button_downloads) self.stackedWidget.setCurrentIndex(PAGE_DOWNLOADS) self.navigation_stack = [] self.hide_left_menu_playlist() def clicked_menu_button_debug(self): self.debug_window = DebugWindow(self.tribler_settings) self.debug_window.show() def clicked_menu_button_subscriptions(self): self.deselect_all_menu_buttons(self.left_menu_button_subscriptions) self.subscribed_channels_page.load_subscribed_channels() self.stackedWidget.setCurrentIndex(PAGE_SUBSCRIBED_CHANNELS) self.navigation_stack = [] self.hide_left_menu_playlist() def hide_left_menu_playlist(self): self.left_menu_seperator.setHidden(True) self.left_menu_playlist_label.setHidden(True) self.left_menu_playlist.setHidden(True) def show_left_menu_playlist(self): self.left_menu_seperator.setHidden(False) self.left_menu_playlist_label.setHidden(False) self.left_menu_playlist.setHidden(False) def on_channel_item_click(self, channel_list_item): list_widget = channel_list_item.listWidget() from TriblerGUI.widgets.channel_list_item import ChannelListItem if isinstance(list_widget.itemWidget(channel_list_item), ChannelListItem): channel_info = channel_list_item.data(Qt.UserRole) self.channel_page.initialize_with_channel(channel_info) self.navigation_stack.append(self.stackedWidget.currentIndex()) self.stackedWidget.setCurrentIndex(PAGE_CHANNEL_DETAILS) def on_playlist_item_click(self, playlist_list_item): list_widget = playlist_list_item.listWidget() from TriblerGUI.widgets.playlist_list_item import PlaylistListItem if isinstance(list_widget.itemWidget(playlist_list_item), PlaylistListItem): playlist_info = playlist_list_item.data(Qt.UserRole) self.playlist_page.initialize_with_playlist(playlist_info) self.navigation_stack.append(self.stackedWidget.currentIndex()) self.stackedWidget.setCurrentIndex(PAGE_PLAYLIST_DETAILS) def on_page_back_clicked(self): prev_page = self.navigation_stack.pop() self.stackedWidget.setCurrentIndex(prev_page) def on_edit_channel_clicked(self): self.stackedWidget.setCurrentIndex(PAGE_EDIT_CHANNEL) self.navigation_stack = [] self.channel_page.on_edit_channel_clicked() def resizeEvent(self, _): # Resize home page cells cell_width = self.home_page_table_view.width( ) / 3 - 3 # We have some padding to the right cell_height = cell_width / 2 + 60 for i in range(0, 3): self.home_page_table_view.setColumnWidth(i, cell_width) self.home_page_table_view.setRowHeight(i, cell_height) self.resize_event.emit() def exit_full_screen(self): self.top_bar.show() self.left_menu.show() self.video_player_page.is_full_screen = False self.showNormal() def close_tribler(self): if not self.core_manager.shutting_down: self.show_loading_screen() 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() def closeEvent(self, close_event): self.close_tribler() close_event.ignore() def keyReleaseEvent(self, event): if event.key() == Qt.Key_Escape: self.escape_pressed.emit() if self.isFullScreen(): self.exit_full_screen()
class MarketWalletsPage(QWidget): """ This page displays information about wallets. """ def __init__(self): QWidget.__init__(self) self.request_mgr = None self.initialized = False self.wallets_to_create = [] self.wallets = None self.dialog = None def initialize_wallets_page(self): if not self.initialized: self.window().wallets_back_button.setIcon( QIcon(get_image_path('page_back.png'))) self.window().wallet_btc_overview_button.clicked.connect( lambda: self.load_transactions('BTC')) self.window().wallet_mc_overview_button.clicked.connect( lambda: self.load_transactions('MC')) self.window().wallet_paypal_overview_button.clicked.connect( lambda: self.load_transactions('PP')) self.window().wallet_abn_overview_button.clicked.connect( lambda: self.load_transactions('ABNA')) self.window().wallet_rabo_overview_button.clicked.connect( lambda: self.load_transactions('RABO')) self.window().add_wallet_button.clicked.connect( self.on_add_wallet_clicked) self.window().wallet_mc_overview_button.hide() self.window().wallet_btc_overview_button.hide() self.window().wallet_paypal_overview_button.hide() self.window().wallet_abn_overview_button.hide() self.window().wallet_rabo_overview_button.hide() self.initialized = True self.load_wallets() def load_wallets(self): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("wallets", self.on_wallets) def on_wallets(self, wallets): self.wallets = wallets["wallets"] if 'MC' in self.wallets and self.wallets["MC"]["created"]: self.window().wallet_mc_overview_button.show() if 'BTC' in self.wallets and self.wallets["BTC"]["created"]: self.window().wallet_btc_overview_button.show() if 'PP' in self.wallets and self.wallets["PP"]["created"]: self.window().wallet_paypal_overview_button.show() if 'ABNA' in self.wallets and self.wallets["ABNA"]["created"]: self.window().wallet_abn_overview_button.show() if 'RABO' in self.wallets and self.wallets["RABO"]["created"]: self.window().wallet_rabo_overview_button.show() # Find out which wallets we still can create self.wallets_to_create = [] for identifier, wallet in self.wallets.iteritems(): if not wallet["created"]: self.wallets_to_create.append(identifier) if len(self.wallets_to_create) > 0: self.window().add_wallet_button.setEnabled(True) def load_transactions(self, wallet_id): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("wallets/%s/transactions" % wallet_id, self.on_transactions) def on_transactions(self, transactions): self.window().wallet_transactions_list.clear() for transaction in transactions["transactions"]: item = QTreeWidgetItem(self.window().wallet_transactions_list) item.setText(0, "Sent" if transaction["outgoing"] else "Received") item.setText(1, transaction["from"]) item.setText(2, transaction["to"]) item.setText( 3, "%g %s" % (transaction["amount"], transaction["currency"])) item.setText( 4, "%g %s" % (transaction["fee_amount"], transaction["currency"])) item.setText(5, transaction["id"]) item.setText(6, transaction["timestamp"]) self.window().wallet_transactions_list.addTopLevelItem(item) def on_add_wallet_clicked(self): menu = TriblerActionMenu(self) for wallet_id in self.wallets_to_create: wallet_action = QAction(self.wallets[wallet_id]['name'], self) wallet_action.triggered.connect( lambda _, wid=wallet_id: self.should_create_wallet(wid)) menu.addAction(wallet_action) menu.exec_(QCursor.pos()) def should_create_wallet(self, wallet_id): if wallet_id == 'BTC': self.dialog = ConfirmationDialog( self, "Create Bitcoin wallet", "Please enter the password of your Bitcoin wallet below:", [('CREATE', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)], show_input=True) self.dialog.dialog_widget.dialog_input.setPlaceholderText( 'Wallet password') self.dialog.button_clicked.connect( self.on_create_btc_wallet_dialog_done) self.dialog.show() else: self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("wallets/%s" % wallet_id, self.on_wallet_created, method='PUT', data='') def on_create_btc_wallet_dialog_done(self, action): password = self.dialog.dialog_widget.dialog_input.text() if action == 1: # Remove the dialog right now self.dialog.setParent(None) self.dialog = None elif action == 0: self.dialog.buttons[0].setEnabled(False) self.dialog.buttons[1].setEnabled(False) self.dialog.buttons[0].setText("CREATING...") self.request_mgr = TriblerRequestManager() post_data = str("password=%s" % password) self.request_mgr.perform_request("wallets/btc", self.on_wallet_created, method='PUT', data=post_data) def on_wallet_created(self, response): if self.dialog: self.dialog.setParent(None) self.dialog = None self.load_wallets()
class MarketOrdersPage(QWidget): """ This page displays orders in the decentralized market in Tribler. """ def __init__(self): QWidget.__init__(self) self.request_mgr = None self.initialized = False self.selected_item = None self.dialog = None self.wallets = {} def initialize_orders_page(self, wallets): if not self.initialized: self.window().orders_back_button.setIcon(QIcon(get_image_path('page_back.png'))) self.window().market_orders_list.sortItems(0, Qt.AscendingOrder) self.window().market_orders_list.customContextMenuRequested.connect(self.on_right_click_order) self.initialized = True self.wallets = wallets self.load_orders() def load_orders(self): self.window().market_orders_list.clear() self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("market/orders", self.on_received_orders) def on_received_orders(self, orders): if not orders: return for order in orders["orders"]: if self.wallets: asset1_prec = self.wallets[order["assets"]["first"]["type"]]["precision"] asset2_prec = self.wallets[order["assets"]["second"]["type"]]["precision"] item = OrderWidgetItem(self.window().market_orders_list, order, asset1_prec, asset2_prec) self.window().market_orders_list.addTopLevelItem(item) def on_right_click_order(self, pos): item_clicked = self.window().market_orders_list.itemAt(pos) if not item_clicked: return self.selected_item = item_clicked if self.selected_item.order['status'] == 'open': # We can only cancel an open order menu = TriblerActionMenu(self) cancel_action = QAction('Cancel order', self) cancel_action.triggered.connect(self.on_cancel_order_clicked) menu.addAction(cancel_action) menu.exec_(self.window().market_orders_list.mapToGlobal(pos)) def on_cancel_order_clicked(self): self.dialog = ConfirmationDialog(self, "Cancel order", "Are you sure you want to cancel the order with id %s?" % self.selected_item.order['order_number'], [('NO', BUTTON_TYPE_NORMAL), ('YES', BUTTON_TYPE_CONFIRM)]) self.dialog.button_clicked.connect(self.on_confirm_cancel_order) self.dialog.show() def on_confirm_cancel_order(self, action): if action == 1: self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("market/orders/%s/cancel" % self.selected_item.order['order_number'], self.on_order_cancelled, method='POST') self.dialog.close_dialog() self.dialog = None def on_order_cancelled(self, response): if not response: return self.load_orders()
class SubscribedChannelsPage(QWidget): """ This page shows all the channels that the user has subscribed to. """ def __init__(self): QWidget.__init__(self) self.dialog = None self.request_mgr = None def initialize(self): self.window().add_subscription_button.clicked.connect( self.on_add_subscription_clicked) def load_subscribed_channels(self): self.window().subscribed_channels_list.set_data_items([ (LoadingListItem, None) ]) self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("channels/subscribed", self.received_subscribed_channels) def received_subscribed_channels(self, results): if not results: return self.window().subscribed_channels_list.set_data_items([]) items = [] if len(results['subscribed']) == 0: self.window().subscribed_channels_list.set_data_items([ (LoadingListItem, "You are not subscribed to any channel.") ]) return for result in results['subscribed']: items.append((ChannelListItem, result)) self.window().subscribed_channels_list.set_data_items(items) def on_add_subscription_clicked(self): self.dialog = ConfirmationDialog( self, "Add subscribed channel", "Please enter the identifier of the channel you want to subscribe to below. " "It can take up to a minute before the channel is visible in your list of " "subscribed channels.", [('ADD', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)], show_input=True) self.dialog.dialog_widget.dialog_input.setPlaceholderText( 'Channel identifier') self.dialog.button_clicked.connect(self.on_subscription_added) self.dialog.show() def on_subscription_added(self, action): if action == 0: self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request( "channels/subscribed/%s" % self.dialog.dialog_widget.dialog_input.text(), self.on_channel_subscribed, method='PUT') self.dialog.close_dialog() self.dialog = None def on_channel_subscribed(self, _): pass
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 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 SettingsPage(QWidget): """ This class is responsible for displaying and adjusting the settings present in Tribler. """ def __init__(self): QWidget.__init__(self) self.settings = None self.settings_request_mgr = None self.saved_dialog = None def initialize_settings_page(self): self.window().settings_tab.initialize() self.window().settings_tab.clicked_tab_button.connect( self.clicked_tab_button) self.window().settings_save_button.clicked.connect(self.save_settings) self.window().download_location_chooser_button.clicked.connect( self.on_choose_download_dir_clicked) self.window().watch_folder_chooser_button.clicked.connect( self.on_choose_watch_dir_clicked) self.window().developer_mode_enabled_checkbox.stateChanged.connect( self.on_developer_mode_checkbox_changed) self.window().download_settings_anon_checkbox.stateChanged.connect( self.on_anon_download_state_changed) self.window().log_location_chooser_button.clicked.connect( self.on_choose_log_dir_clicked) def on_developer_mode_checkbox_changed(self, _): self.window().gui_settings.setValue( "debug", self.window().developer_mode_enabled_checkbox.isChecked()) self.window().left_menu_button_debug.setHidden( not self.window().developer_mode_enabled_checkbox.isChecked()) def on_anon_download_state_changed(self, _): if self.window().download_settings_anon_checkbox.isChecked(): self.window().download_settings_anon_seeding_checkbox.setChecked( True) self.window().download_settings_anon_seeding_checkbox.setEnabled( not self.window().download_settings_anon_checkbox.isChecked()) def on_choose_download_dir_clicked(self): previous_download_path = self.window().download_location_input.text( ) or "" download_dir = QFileDialog.getExistingDirectory( self.window(), "Please select the download location", previous_download_path, QFileDialog.ShowDirsOnly) if not download_dir: return self.window().download_location_input.setText(download_dir) def on_choose_watch_dir_clicked(self): if self.window().watchfolder_enabled_checkbox.isChecked(): previous_watch_dir = self.window().watchfolder_location_input.text( ) or "" watch_dir = QFileDialog.getExistingDirectory( self.window(), "Please select the watch folder", previous_watch_dir, QFileDialog.ShowDirsOnly) if not watch_dir: return self.window().watchfolder_location_input.setText(watch_dir) def on_choose_log_dir_clicked(self): previous_log_dir = self.window().log_location_input.text() or "" log_dir = QFileDialog.getExistingDirectory( self.window(), "Please select the log directory", previous_log_dir, QFileDialog.ShowDirsOnly) if not log_dir or log_dir == previous_log_dir: return if not is_dir_writable(log_dir): ConfirmationDialog.show_message( self.dialog_widget, "Insufficient Permissions", "<i>%s</i> is not writable. " % log_dir, "OK") else: self.window().log_location_input.setText(log_dir) def initialize_with_settings(self, settings): self.settings = settings settings = settings["settings"] gui_settings = self.window().gui_settings # General settings self.window().developer_mode_enabled_checkbox.setChecked( get_gui_setting(gui_settings, "debug", False, is_bool=True)) self.window().family_filter_checkbox.setChecked( settings['general']['family_filter']) self.window().download_location_input.setText( settings['downloadconfig']['saveas']) self.window().always_ask_location_checkbox.setChecked( get_gui_setting(gui_settings, "ask_download_settings", True, is_bool=True)) self.window().download_settings_anon_checkbox.setChecked( get_gui_setting(gui_settings, "default_anonymity_enabled", True, is_bool=True)) self.window().download_settings_anon_seeding_checkbox.setChecked( get_gui_setting(gui_settings, "default_safeseeding_enabled", True, is_bool=True)) self.window().watchfolder_enabled_checkbox.setChecked( settings['watch_folder']['enabled']) self.window().watchfolder_location_input.setText( settings['watch_folder']['watch_folder_dir']) # Log directory self.window().log_location_input.setText( settings['general']['log_dir']) # Connection settings self.window().firewall_current_port_input.setText( str(settings['general']['minport'])) self.window().lt_proxy_type_combobox.setCurrentIndex( settings['libtorrent']['lt_proxytype']) if settings['libtorrent']['lt_proxyserver']: self.window().lt_proxy_server_input.setText( settings['libtorrent']['lt_proxyserver'][0]) self.window().lt_proxy_port_input.setText( "%s" % settings['libtorrent']['lt_proxyserver'][1]) if settings['libtorrent']['lt_proxyauth']: self.window().lt_proxy_username_input.setText( settings['libtorrent']['lt_proxyauth'][0]) self.window().lt_proxy_password_input.setText( settings['libtorrent']['lt_proxyauth'][1]) self.window().lt_utp_checkbox.setChecked(settings['libtorrent']['utp']) max_conn_download = settings['libtorrent']['max_connections_download'] if max_conn_download == -1: max_conn_download = 0 self.window().max_connections_download_input.setText( str(max_conn_download)) # Bandwidth settings self.window().upload_rate_limit_input.setText( str(settings['libtorrent']['max_upload_rate'] / 1024)) self.window().download_rate_limit_input.setText( str(settings['libtorrent']['max_download_rate'] / 1024)) # Seeding settings getattr( self.window(), "seeding_" + settings['downloadconfig']['seeding_mode'] + "_radio").setChecked(True) self.window().seeding_time_input.setText( seconds_to_hhmm_string(settings['downloadconfig']['seeding_time'])) ind = self.window().seeding_ratio_combobox.findText( str(settings['downloadconfig']['seeding_ratio'])) if ind != -1: self.window().seeding_ratio_combobox.setCurrentIndex(ind) # Anonymity settings self.window().allow_exit_node_checkbox.setChecked( settings['tunnel_community']['exitnode_enabled']) self.window().number_hops_slider.setValue( int(settings['Tribler']['default_number_hops']) - 1) self.window().multichain_enabled_checkbox.setChecked( settings['multichain']['enabled']) def load_settings(self): self.settings_request_mgr = TriblerRequestManager() self.settings_request_mgr.perform_request( "settings", self.initialize_with_settings) def clicked_tab_button(self, tab_button_name): if tab_button_name == "settings_general_button": self.window().settings_stacked_widget.setCurrentIndex( PAGE_SETTINGS_GENERAL) elif tab_button_name == "settings_connection_button": self.window().settings_stacked_widget.setCurrentIndex( PAGE_SETTINGS_CONNECTION) elif tab_button_name == "settings_bandwidth_button": self.window().settings_stacked_widget.setCurrentIndex( PAGE_SETTINGS_BANDWIDTH) elif tab_button_name == "settings_seeding_button": self.window().settings_stacked_widget.setCurrentIndex( PAGE_SETTINGS_SEEDING) elif tab_button_name == "settings_anonymity_button": self.window().settings_stacked_widget.setCurrentIndex( PAGE_SETTINGS_ANONYMITY) def save_settings(self): # Create a dictionary with all available settings settings_data = { 'general': {}, 'Tribler': {}, 'downloadconfig': {}, 'libtorrent': {}, 'watch_folder': {}, 'tunnel_community': {}, 'multichain': {} } settings_data['general']['family_filter'] = self.window( ).family_filter_checkbox.isChecked() settings_data['downloadconfig']['saveas'] = self.window( ).download_location_input.text().encode('utf-8') settings_data['general']['log_dir'] = self.window( ).log_location_input.text() settings_data['watch_folder']['enabled'] = self.window( ).watchfolder_enabled_checkbox.isChecked() if settings_data['watch_folder']['enabled']: settings_data['watch_folder']['watch_folder_dir'] = self.window( ).watchfolder_location_input.text() settings_data['general']['minport'] = self.window( ).firewall_current_port_input.text() settings_data['libtorrent']['lt_proxytype'] = self.window( ).lt_proxy_type_combobox.currentIndex() settings_data['libtorrent']['lt_proxyserver'] = None if self.window().lt_proxy_server_input.text() and len( self.window().lt_proxy_port_input.text()) > 0: settings_data['libtorrent']['lt_proxyserver'] = [ self.window().lt_proxy_server_input.text(), None ] # The port should be a number try: lt_proxy_port = int(self.window().lt_proxy_port_input.text()) settings_data['libtorrent']['lt_proxyserver'][ 1] = lt_proxy_port except ValueError: ConfirmationDialog.show_error( self.window(), "Invalid proxy port number", "You've entered an invalid format for the proxy port number. " "Please enter a whole number.") return if len(self.window().lt_proxy_username_input.text()) > 0 and \ len(self.window().lt_proxy_password_input.text()) > 0: settings_data['libtorrent']['lt_proxyauth'] = [None, None] settings_data['libtorrent']['lt_proxyauth'][0] = self.window( ).lt_proxy_username_input.text() settings_data['libtorrent']['lt_proxyauth'][1] = self.window( ).lt_proxy_password_input.text() settings_data['libtorrent']['utp'] = self.window( ).lt_utp_checkbox.isChecked() try: max_conn_download = int( self.window().max_connections_download_input.text()) except ValueError: ConfirmationDialog.show_error( self.window(), "Invalid number of connections", "You've entered an invalid format for the maximum number of connections. " "Please enter a whole number.") return if max_conn_download == 0: max_conn_download = -1 settings_data['libtorrent'][ 'max_connections_download'] = max_conn_download try: if self.window().upload_rate_limit_input.text(): user_upload_rate_limit = int( self.window().upload_rate_limit_input.text()) * 1024 if user_upload_rate_limit < sys.maxsize: settings_data['libtorrent'][ 'max_upload_rate'] = user_upload_rate_limit else: raise ValueError if self.window().download_rate_limit_input.text(): user_download_rate_limit = int( self.window().download_rate_limit_input.text()) * 1024 if user_download_rate_limit < sys.maxsize: settings_data['libtorrent'][ 'max_download_rate'] = user_download_rate_limit else: raise ValueError except ValueError: ConfirmationDialog.show_error( self.window(), "Invalid value for bandwidth limit", "You've entered an invalid value for the maximum upload/download rate. " "Please enter a whole number (max: %d)" % (sys.maxsize / 1000)) return seeding_modes = ['forever', 'time', 'never', 'ratio'] selected_mode = 'forever' for seeding_mode in seeding_modes: if getattr(self.window(), "seeding_" + seeding_mode + "_radio").isChecked(): selected_mode = seeding_mode break settings_data['downloadconfig']['seeding_mode'] = selected_mode settings_data['downloadconfig']['seeding_ratio'] = self.window( ).seeding_ratio_combobox.currentText() try: settings_data['downloadconfig'][ 'seeding_time'] = string_to_seconds( self.window().seeding_time_input.text()) except ValueError: ConfirmationDialog.show_error( self.window(), "Invalid seeding time", "You've entered an invalid format for the seeding time (expected HH:MM)" ) return settings_data['tunnel_community']['exitnode_enabled'] = self.window( ).allow_exit_node_checkbox.isChecked() settings_data['Tribler']['default_number_hops'] = self.window( ).number_hops_slider.value() + 1 settings_data['multichain']['enabled'] = self.window( ).multichain_enabled_checkbox.isChecked() self.window().settings_save_button.setEnabled(False) self.settings_request_mgr = TriblerRequestManager() self.settings_request_mgr.perform_request( "settings", self.on_settings_saved, method='POST', data=json.dumps(settings_data)) def on_settings_saved(self, _): # Now save the GUI settings self.window().gui_settings.setValue( "ask_download_settings", self.window().always_ask_location_checkbox.isChecked()) self.window().gui_settings.setValue( "default_anonymity_enabled", self.window().download_settings_anon_checkbox.isChecked()) self.window().gui_settings.setValue( "default_safeseeding_enabled", self.window().download_settings_anon_seeding_checkbox.isChecked()) self.saved_dialog = ConfirmationDialog( TriblerRequestManager.window, "Settings saved", "Your settings have been saved.", [('CLOSE', BUTTON_TYPE_NORMAL)]) self.saved_dialog.button_clicked.connect(self.on_dialog_cancel_clicked) self.saved_dialog.show() self.window().fetch_settings() def on_dialog_cancel_clicked(self, _): self.window().settings_save_button.setEnabled(True) self.saved_dialog.setParent(None) self.saved_dialog = None
class TriblerRequestManager(QNetworkAccessManager): """ This class is responsible for all the requests made to the Tribler REST API. """ window = None received_json = pyqtSignal(object, int) received_file = pyqtSignal(str, object) def __init__(self): QNetworkAccessManager.__init__(self) self.request_id = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10)) self.base_url = "http://localhost:%d/" % API_PORT self.reply = None self.error_dialog = None def perform_request(self, endpoint, read_callback, data="", method='GET', capture_errors=True): """ Perform a HTTP request. :param endpoint: the endpoint to call (i.e. "statistics") :param read_callback: the callback to be called with result info when we have the data :param data: optional POST data to be sent with the request :param method: the HTTP verb (GET/POST/PUT/PATCH) :param capture_errors: whether errors should be handled by this class (defaults to True) """ performed_requests[self.request_id] = [endpoint, method, data, time(), -1] url = self.base_url + endpoint if method == 'GET': buf = QBuffer() buf.setData(data) buf.open(QIODevice.ReadOnly) get_request = QNetworkRequest(QUrl(url)) self.reply = self.sendCustomRequest(get_request, "GET", buf) buf.setParent(self.reply) elif method == 'PATCH': buf = QBuffer() buf.setData(data) buf.open(QIODevice.ReadOnly) patch_request = QNetworkRequest(QUrl(url)) self.reply = self.sendCustomRequest(patch_request, "PATCH", buf) buf.setParent(self.reply) elif method == 'PUT': request = QNetworkRequest(QUrl(url)) request.setHeader(QNetworkRequest.ContentTypeHeader, "application/x-www-form-urlencoded") self.reply = self.put(request, data) elif method == 'DELETE': buf = QBuffer() buf.setData(data) buf.open(QIODevice.ReadOnly) delete_request = QNetworkRequest(QUrl(url)) self.reply = self.sendCustomRequest(delete_request, "DELETE", buf) buf.setParent(self.reply) elif method == 'POST': request = QNetworkRequest(QUrl(url)) request.setHeader(QNetworkRequest.ContentTypeHeader, "application/x-www-form-urlencoded") self.reply = self.post(request, data) if read_callback: self.received_json.connect(read_callback) self.finished.connect(lambda reply: self.on_finished(reply, capture_errors)) def on_finished(self, reply, capture_errors): performed_requests[self.request_id][4] = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) data = reply.readAll() try: json_result = json.loads(str(data)) if 'error' in json_result and capture_errors: if isinstance(json_result['error'], (str, unicode)): self.show_error(json_result['error']) elif 'message' in json_result['error']: self.show_error(json_result['error']['message']) else: self.received_json.emit(json_result, reply.error()) except ValueError: self.received_json.emit(None, reply.error()) logging.error("No json object could be decoded from data: %s" % data) def download_file(self, endpoint, read_callback): url = self.base_url + endpoint self.reply = self.get(QNetworkRequest(QUrl(url))) self.received_file.connect(read_callback) self.finished.connect(self.on_file_download_finished) def on_file_download_finished(self, reply): content_header = str(reply.rawHeader("Content-Disposition")) data = reply.readAll() self.received_file.emit(content_header.split("=")[1], data) def show_error(self, error_text): main_text = "An error occurred during the request:\n\n%s" % error_text self.error_dialog = ConfirmationDialog(TriblerRequestManager.window, "Request error", main_text, [('close', BUTTON_TYPE_NORMAL)]) self.error_dialog.button_clicked.connect(self.on_error_dialog_cancel_clicked) self.error_dialog.show() def on_error_dialog_cancel_clicked(self, _): self.error_dialog.setParent(None) self.error_dialog = None def cancel_request(self): if self.reply: self.reply.abort()