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
Example #2
0
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()
Example #3
0
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()
Example #4
0
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
Example #5
0
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))
Example #6
0
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
Example #7
0
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))
Example #8
0
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)
Example #9
0
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))
Example #10
0
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))
Example #11
0
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 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
Example #13
0
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.close_dialog()
        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.close_dialog()
        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.close_dialog()
        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.close_dialog()
        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.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 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 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)
Example #14
0
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()
Example #15
0
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()
Example #16
0
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()
Example #17
0
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()