Esempio n. 1
0
    def __init__(self, api_port, api_key, error_handler):
        QObject.__init__(self, None)

        self._logger = logging.getLogger(self.__class__.__name__)

        self.base_path = get_base_path()
        if not is_frozen():
            self.base_path = os.path.join(get_base_path(), "..")

        root_state_dir = get_root_state_directory()
        self.version_history = VersionHistory(root_state_dir)

        self.core_process = None
        self.api_port = api_port
        self.api_key = api_key
        self.events_manager = EventRequestManager(self.api_port, self.api_key,
                                                  error_handler)

        self.shutting_down = False
        self.should_stop_on_shutdown = False
        self.use_existing_core = True
        self.is_core_running = False
        self.core_traceback = None
        self.core_traceback_timestamp = 0

        self.check_state_timer = QTimer()
        self.check_state_timer.setSingleShot(True)
        connect(self.check_state_timer.timeout, self.check_core_ready)
Esempio n. 2
0
def test_get_disposable_state_directories(tmpdir_factory):
    tmpdir = tmpdir_factory.mktemp("scenario")
    root_state_dir = Path(tmpdir)

    # Scenario: multiple versions of state directory exists, <major.minor.version>. Then on disposable directories
    # based on the current version show all other directories except the last version.

    major_versions = [8, 7]
    minor_versions = list(range(10))
    patch_versions = list(range(3))

    last_version = "8.9.2"
    last_version_dir = root_state_dir / "8.9"
    second_last_version_dir = root_state_dir / "8.8"

    version_history = {"last_version": last_version, "history": dict()}
    base_install_ts = time.time() - 1000  # some timestamp in the past

    # Create state directories for all older versions
    for major in major_versions:
        for minor in reversed(minor_versions):
            for patch in patch_versions:
                version = f"{major}.{minor}.{patch}"
                version_dir = f"{major}.{minor}"

                # Set install time in order of version. i.e. newer version are installed later
                version_install_ts = base_install_ts + major * 100 + minor * 10 + patch
                version_history["history"][version_install_ts] = version

                # Create an empty version directory if does not exist
                (root_state_dir / version_dir).mkdir(exist_ok=True)

    unused1 = root_state_dir / "unused_v8.9_1234567"
    unused2 = root_state_dir / "unused_v9.0_7654321"
    unused1.mkdir()
    unused2.mkdir()

    # Write the version history file before checking disposable directories
    (root_state_dir / VERSION_HISTORY_FILENAME).write_text(
        json.dumps(version_history))

    code_version_id = "9.0.0"
    history = VersionHistory(root_state_dir, code_version_id)

    # Case 1: Skip last two versions, then those two last directories will not returned as disposable dirs.
    disposable_dirs = history.get_disposable_state_directories()
    assert last_version_dir in disposable_dirs
    assert second_last_version_dir in disposable_dirs
    assert unused1 in disposable_dirs
    assert unused2 in disposable_dirs

    # Case 2: Skip only one version
    disposable_dirs = history.get_disposable_state_directories(
        skip_versions=1, include_unused=False)
    assert last_version_dir not in disposable_dirs
    assert second_last_version_dir in disposable_dirs
    assert unused1 not in disposable_dirs
    assert unused2 not in disposable_dirs
Esempio n. 3
0
 def __init__(self):
     QWidget.__init__(self)
     self.settings = None
     self.version_history = VersionHistory(get_root_state_directory())
     self.lang_list = sorted([
         lang_name
         for lang_name, lang_code in AVAILABLE_TRANSLATIONS.items()
     ])
     self.lang_list.insert(0, tr("System default"))
Esempio n. 4
0
def test_installed_versions_and_removal(tmpdir_factory):
    tmpdir = tmpdir_factory.mktemp("install_version_test")
    root_state_dir = Path(tmpdir)

    # create current version directory
    code_version_id = "8.9.10"
    current_version_dir = root_state_dir / "8.9"
    current_version_dir.mkdir()

    major_versions = [7, 8]
    minor_versions = [5, 6, 7, 8]

    version_history = {"last_version": "7.8", "history": dict()}
    base_install_ts = time.time() - 1000  # some timestamp in the past

    for major in major_versions:
        for minor in minor_versions:
            version_str = f"{major}.{minor}"
            (root_state_dir / version_str).mkdir(exist_ok=True)
            # Set install time in order of version. i.e. newer version are installed later
            version_install_ts = base_install_ts + major * 100 + minor * 10
            version_history["history"][version_install_ts] = version_str

    (root_state_dir / VERSION_HISTORY_FILENAME).write_text(
        json.dumps(version_history))

    history = VersionHistory(root_state_dir, code_version_id)

    # 1. Default values
    installed_versions = history.get_installed_versions()
    assert history.code_version in installed_versions
    assert len(installed_versions) == len(major_versions) * len(
        minor_versions) + 1  # including the current version

    # 2. exclude current version
    installed_versions = history.get_installed_versions(
        with_code_version=False)
    assert history.code_version not in installed_versions
    assert len(installed_versions) == len(major_versions) * len(
        minor_versions)  # the current version not included

    # 3. Delete a few versions
    history.versions[7, 5].delete_state()
    history.versions[7, 6].delete_state()

    installed_versions = history.get_installed_versions(
        with_code_version=False)
    assert current_version_dir not in installed_versions
    assert len(
        installed_versions) == len(major_versions) * len(minor_versions) - 2
Esempio n. 5
0
def test_get_last_upgradable_version_based_on_dir(tmpdir):
    """
    Scenario: 5 versions in the history file, but state directory only for one of those exists.
    The second version in the list has higher version than the current one, and has dir too.
    Test that only the most recent lower version will be selected as the upgrade source.
    """
    root_state_dir = Path(tmpdir)
    json_dict = {"last_version": "100.1.1", "history": dict()}
    json_dict["history"]["1"] = "100.1.1"  # no dir - bad
    json_dict["history"]["2"] = "99.2.3"  # dir in place, earlier than 3 - bad
    (root_state_dir / "102.1").mkdir()
    json_dict["history"][
        "3"] = "102.1.0"  # version OK, got dir, same major/minor version as us - ignore
    (root_state_dir / "99.2").mkdir()
    json_dict["history"][
        "4"] = "92.3.4"  # dir in place, more recent than 2, - good
    (root_state_dir / "92.3").mkdir()
    json_dict["history"]["5"] = "200.2.3"  # version higher than code version
    (root_state_dir / "200.2").mkdir()
    json_dict["history"]["6"] = "94.3.4"  # version OK, no dir - bad

    (root_state_dir / VERSION_HISTORY_FILENAME).write_text(
        json.dumps(json_dict))

    history = VersionHistory(root_state_dir, code_version_id="102.1.1")
    assert history.code_version.can_be_copied_from is not None
    assert history.code_version.can_be_copied_from.version_str == "92.3.4"
Esempio n. 6
0
def upgrade_state_dir(root_state_dir: Path,
                      update_status_callback=None,
                      interrupt_upgrade_event=None):
    # Before any upgrade, prepare a separate state directory for the update version so it does not
    # affect the older version state directory. This allows for safe rollback.
    version_history = VersionHistory(root_state_dir)
    version_history.fork_state_directory_if_necessary()
    version_history.save_if_necessary()
    state_dir = version_history.code_version.directory
    if not state_dir.exists():
        return

    config = TriblerConfig.load(file=state_dir / CONFIG_FILE_NAME,
                                state_dir=state_dir,
                                reset_config_on_error=True)
    channels_dir = config.chant.get_path_as_absolute('channels_dir',
                                                     config.state_dir)

    primary_private_key_path = config.state_dir / KeyComponent.get_private_key_filename(
        config)
    primary_public_key_path = config.state_dir / config.trustchain.ec_keypair_pubfilename
    primary_key = KeyComponent.load_or_create(primary_private_key_path,
                                              primary_public_key_path)

    upgrader = TriblerUpgrader(state_dir,
                               channels_dir,
                               primary_key,
                               update_status_callback=update_status_callback,
                               interrupt_upgrade_event=interrupt_upgrade_event)
    upgrader.run()
Esempio n. 7
0
    async def start_tribler():
        # Check if we are already running a Tribler instance
        process_checker = ProcessChecker(root_state_dir)
        if process_checker.already_running:
            return
        process_checker.create_lock_file()

        # Before any upgrade, prepare a separate state directory for the update version so it does not
        # affect the older version state directory. This allows for safe rollback.
        version_history = VersionHistory(root_state_dir)
        version_history.fork_state_directory_if_necessary()
        version_history.save_if_necessary()
        state_dir = version_history.code_version.directory

        config = TriblerConfig(state_dir,
                               config_file=state_dir / CONFIG_FILENAME,
                               reset_config_on_error=True)

        if not config.get_core_error_reporting_requires_user_consent():
            SentryReporter.global_strategy = SentryStrategy.SEND_ALLOWED

        config.set_api_http_port(int(api_port))
        # If the API key is set to an empty string, it will remain disabled
        if config.get_api_key() not in ('', api_key):
            config.set_api_key(api_key)
            config.write(
            )  # Immediately write the API key so other applications can use it
        config.set_api_http_enabled(True)

        priority_order = config.get_cpu_priority_order()
        set_process_priority(pid=os.getpid(), priority_order=priority_order)

        global trace_logger
        # Enable tracer if --trace-debug or --trace-exceptions flag is present in sys.argv
        trace_logger = check_and_enable_code_tracing('core',
                                                     config.get_log_dir())

        session = Session(config, core_test_mode=core_test_mode)

        signal.signal(signal.SIGTERM,
                      lambda signum, stack: shutdown(session, signum, stack))
        await session.start()
Esempio n. 8
0
def test_read_write_version_history(tmpdir):
    root_path = Path(tmpdir)
    history = VersionHistory(root_path, code_version_id='100.100.100')

    assert history.root_state_dir == root_path
    assert history.file_path == root_path / "version_history.json"
    assert history.file_data == {"last_version": None, "history": {}}

    # If there is no version history file, no information about last version is available
    assert history.last_run_version is None
    assert not history.versions

    assert history.code_version.version_str == '100.100.100'
    assert history.code_version.major_minor == (100, 100)

    # Saving and loading the version again
    history.save()

    history2 = VersionHistory(root_path, code_version_id='100.100.100')
    # version was not added to history as the state directory does not exist
    assert history2.last_run_version is None
    assert not history2.versions
    assert not history2.versions_by_number
    assert not history2.versions_by_time

    state_dir: Path = root_path / "100.100"
    state_dir.mkdir()

    history3 = VersionHistory(root_path, code_version_id='100.100.100')
    assert history3.last_run_version is not None
    assert history3.last_run_version.version_str == '100.100.100'
    assert history3.last_run_version.directory == state_dir
    assert len(history3.versions) == 1
    assert (100, 100) in history3.versions
    assert history3.versions[100, 100] == history3.last_run_version
    assert history3.versions_by_number == [history3.last_run_version]
    assert history3.versions_by_time == [history3.last_run_version]
Esempio n. 9
0
 def __init__(self):
     QWidget.__init__(self)
     self.settings = None
     self.saved_dialog = None
     self.version_history = VersionHistory(get_root_state_directory())
Esempio n. 10
0
class SettingsPage(AddBreadcrumbOnShowMixin, QWidget):
    """
    This class is responsible for displaying and adjusting the settings present in Tribler.
    """
    def __init__(self):
        QWidget.__init__(self)
        self.settings = None
        self.saved_dialog = None
        self.version_history = VersionHistory(get_root_state_directory())

    def initialize_settings_page(self):
        self.window().settings_tab.initialize()
        connect(self.window().settings_tab.clicked_tab_button,
                self.clicked_tab_button)
        connect(self.window().settings_save_button.clicked, self.save_settings)

        connect(self.window().download_location_chooser_button.clicked,
                self.on_choose_download_dir_clicked)
        connect(self.window().watch_folder_chooser_button.clicked,
                self.on_choose_watch_dir_clicked)

        connect(self.window().channel_autocommit_checkbox.stateChanged,
                self.on_channel_autocommit_checkbox_changed)
        connect(self.window().family_filter_checkbox.stateChanged,
                self.on_family_filter_checkbox_changed)
        connect(self.window().developer_mode_enabled_checkbox.stateChanged,
                self.on_developer_mode_checkbox_changed)
        connect(self.window().use_monochrome_icon_checkbox.stateChanged,
                self.on_use_monochrome_icon_checkbox_changed)
        connect(self.window().download_settings_anon_checkbox.stateChanged,
                self.on_anon_download_state_changed)
        connect(self.window().log_location_chooser_button.clicked,
                self.on_choose_log_dir_clicked)
        connect(self.window().btn_remove_old_state_dir.clicked,
                self.on_remove_version_dirs)

        checkbox_style = get_checkbox_style()
        for checkbox in [
                self.window().family_filter_checkbox,
                self.window().channel_autocommit_checkbox,
                self.window().always_ask_location_checkbox,
                self.window().developer_mode_enabled_checkbox,
                self.window().use_monochrome_icon_checkbox,
                self.window().download_settings_anon_checkbox,
                self.window().download_settings_anon_seeding_checkbox,
                self.window().lt_utp_checkbox,
                self.window().watchfolder_enabled_checkbox,
                self.window().allow_exit_node_checkbox,
                self.window().developer_mode_enabled_checkbox,
                self.window().checkbox_enable_network_statistics,
                self.window().checkbox_enable_resource_log,
                self.window().download_settings_add_to_channel_checkbox,
        ]:
            checkbox.setStyleSheet(checkbox_style)

        self.update_stacked_widget_height()

    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, checked):
        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, checked):
        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, checked):
        previous_log_dir = self.window().log_location_input.text() or ""
        log_dir = QFileDialog.getExistingDirectory(
            self.window(), tr("Please select the log directory"),
            previous_log_dir, QFileDialog.ShowDirsOnly)

        if not log_dir or log_dir == previous_log_dir:
            return

        is_writable, error = is_dir_writable(log_dir)
        if not is_writable:
            gui_error_message = f"<i>{log_dir}</i> is not writable. [{error}]"
            ConfirmationDialog.show_message(self.window(),
                                            tr("Insufficient Permissions"),
                                            gui_error_message, "OK")
        else:
            self.window().log_location_input.setText(log_dir)

    def initialize_with_settings(self, settings):
        if not settings:
            return
        self.settings = settings = settings["settings"]
        gui_settings = self.window().gui_settings

        self.window().settings_stacked_widget.show()
        self.window().settings_tab.show()
        self.window().settings_save_button.show()

        # 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().download_settings_add_to_channel_checkbox.setChecked(
            settings['download_defaults']['add_download_to_channel'])
        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(
            f"{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']))
        connect(self.window().number_hops_slider.valueChanged,
                self.update_anonymity_cost_label)
        self.update_anonymity_cost_label(
            int(settings['download_defaults']['number_hops']))

        # Data settings
        self.load_settings_data_tab()

        # 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(
            f"Current Priority = {cpu_priority}")
        connect(self.window().slider_cpu_level.valueChanged,
                self.show_updated_cpu_priority)
        self.window().checkbox_enable_network_statistics.setChecked(
            settings['ipv8']['statistics'])

    def _version_dir_checkbox(self, state_dir, enabled=True):
        dir_size = sum(f.stat().st_size for f in state_dir.glob('**/*'))
        text = f"{state_dir}   {format_size(dir_size)}"
        checkbox = QCheckBox(text)
        checkbox.setEnabled(enabled)
        return checkbox

    def load_settings_data_tab(self):
        self.refresh_current_version_checkbox()
        self.refresh_old_version_checkboxes()

    def refresh_current_version_checkbox(self):
        get_root_state_directory()
        code_version_dir = self.version_history.code_version.directory
        self.refresh_version_checkboxes(self.window().state_dir_current,
                                        [code_version_dir],
                                        enabled=False)

    def refresh_old_version_checkboxes(self):
        get_root_state_directory()
        old_state_dirs = self.version_history.get_disposable_state_directories(
        )
        self.refresh_version_checkboxes(self.window().state_dir_list,
                                        old_state_dirs,
                                        enabled=True)

    def refresh_version_checkboxes(self, parent, old_state_dirs, enabled=True):
        version_dirs_layout = parent.layout()
        for checkbox in parent.findChildren(QCheckBox):
            version_dirs_layout.removeWidget(checkbox)
            checkbox.setParent(None)
            checkbox.deleteLater()

        for state_dir in old_state_dirs:
            version_dir_checkbox = self._version_dir_checkbox(state_dir,
                                                              enabled=enabled)
            version_dirs_layout.addWidget(version_dir_checkbox)

    def on_remove_version_dirs(self, _):
        root_version_dir = str(get_root_state_directory())

        def dir_from_checkbox_text(checkbox):
            # eg text: "/home/<user>/.Tribler/v7.8   5 GB"
            state_dir = checkbox.text().rpartition("   ")[0]
            if not state_dir.startswith(root_version_dir):
                return None  # safety check just for case
            state_dir = state_dir[len(root_version_dir):]
            if state_dir.startswith('/'):
                state_dir = state_dir[1:]
            return state_dir

        dirs_selected_for_deletion = []
        for checkbox in self.window().state_dir_list.findChildren(QCheckBox):
            if checkbox.isChecked():
                state_dir = dir_from_checkbox_text(checkbox)
                if state_dir:
                    dirs_selected_for_deletion.append(state_dir)

        if self.on_confirm_remove_version_dirs(dirs_selected_for_deletion):
            remove_state_dirs(root_version_dir, dirs_selected_for_deletion)
            self.refresh_old_version_checkboxes()

    def on_confirm_remove_version_dirs(self, selected_versions):
        message_box = QMessageBox()
        message_box.setIcon(QMessageBox.Question)

        if selected_versions:
            version_dirs_str = "\n- ".join(selected_versions)
            versions_info = tr("Versions: \n- %s") % version_dirs_str

            title = tr("Confirm delete older versions?")
            message_body = (tr("Are you sure to remove the selected versions? "
                               "\nYou can not undo this action."
                               "\n\n ") % versions_info)
            message_buttons = QMessageBox.No | QMessageBox.Yes
        else:
            title = tr("No versions selected")
            message_body = tr("Select a version to delete.")
            message_buttons = QMessageBox.Close

        message_box.setWindowTitle(title)
        message_box.setText(message_body)
        message_box.setStandardButtons(message_buttons)

        user_choice = message_box.exec_()
        return user_choice == QMessageBox.Yes

    def update_anonymity_cost_label(self, value):
        html_text = tr(
            "<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(
            tr("Current Priority = %s") % value)

    def load_settings(self):
        self.window().settings_stacked_widget.hide()
        self.window().settings_tab.hide()
        self.window().settings_save_button.hide()

        TriblerNetworkRequest("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_data_button":
            self.window().settings_stacked_widget.setCurrentIndex(
                PAGE_SETTINGS_DATA)
        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, checked):
        # Create a dictionary with all available settings
        settings_data = {
            'general': {},
            'Tribler': {},
            'download_defaults': {},
            'libtorrent': {},
            'watch_folder': {},
            'tunnel_community': {},
            'trustchain': {},
            'resource_monitor': {},
            'ipv8': {},
            'chant': {},
        }
        settings_data['download_defaults']['saveas'] = self.window(
        ).download_location_input.text()
        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'] = "{}:{}".format(
                    self.window().lt_proxy_server_input.text(),
                    int(self.window().lt_proxy_port_input.text()),
                )
            except ValueError:
                ConfirmationDialog.show_error(
                    self.window(),
                    tr("Invalid proxy port number"),
                    tr("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'] = "{}:{}".format(
                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(),
                tr("Invalid number of connections"),
                tr("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(
                    float(self.window().upload_rate_limit_input.text()) * 1024)
                if user_upload_rate_limit < MAX_LIBTORRENT_RATE_LIMIT:
                    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(
                    float(self.window().download_rate_limit_input.text()) *
                    1024)
                if user_download_rate_limit < MAX_LIBTORRENT_RATE_LIMIT:
                    settings_data['libtorrent'][
                        'max_download_rate'] = user_download_rate_limit
                else:
                    raise ValueError
        except ValueError:
            ConfirmationDialog.show_error(
                self.window(),
                tr("Invalid value for bandwidth limit"),
                tr("You've entered an invalid value for the maximum upload/download rate. \n"
                   "The rate is specified in KB/s and the value permitted is between 0 and %d KB/s.\n"
                   "Note that the decimal values are truncated.") %
                (MAX_LIBTORRENT_RATE_LIMIT / 1024),
            )
            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(),
                tr("Invalid value for api port"),
                tr("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'] = float(
            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(),
                tr("Invalid seeding time"),
                tr("You've entered an invalid format for the seeding time (expected HH:MM)"
                   ),
            )
            return

        settings_data['tunnel_community']['exitnode_enabled'] = self.window(
        ).allow_exit_node_checkbox.isChecked()
        settings_data['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['download_defaults'][
            'add_download_to_channel'] = self.window(
            ).download_settings_add_to_channel_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)

        # TODO: do it in RESTful style, on the REST return JSON instead
        # In case the default save dir has changed, add it to the top of the list of last download locations.
        # Otherwise, the user could absentmindedly click through the download dialog and start downloading into
        # the last used download dir, and not into the newly designated default download dir.
        if self.settings['download_defaults']['saveas'] != settings_data[
                'download_defaults']['saveas']:
            self.window().update_recent_download_locations(
                settings_data['download_defaults']['saveas'])
        self.settings = settings_data

        TriblerNetworkRequest("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,
            tr("Settings saved"),
            tr("Your settings have been saved."),
            [(tr("CLOSE"), BUTTON_TYPE_NORMAL)],
        )
        connect(self.saved_dialog.button_clicked,
                self.on_dialog_cancel_clicked)
        self.saved_dialog.show()
        self.window().fetch_settings()

    def on_dialog_cancel_clicked(self, _):
        self.window().settings_save_button.setEnabled(True)
        self.saved_dialog.close_dialog()
        self.saved_dialog = None
Esempio n. 11
0
def test_coverage(tmpdir):
    root_state_dir = Path(tmpdir)
    v1 = TriblerVersion(root_state_dir, "7.3.1a")
    assert repr(v1) == '<TriblerVersion{7.3.1a}>'
    with pytest.raises(VersionError, match='Cannot rename root directory'):
        v1.rename_directory("foo")

    v2 = TriblerVersion(root_state_dir, "7.8.1")
    assert v2.directory == root_state_dir / "7.8"
    v2.directory.mkdir()
    v2.rename_directory("renamed")
    assert list(root_state_dir.glob("renamed7.8_*"))
    assert v2.directory == root_state_dir / "7.8"

    v2.directory.mkdir()
    (v2.directory / "foobar.txt").write_text("abc")
    v2.delete_state()
    assert list(root_state_dir.glob("deleted_v7.8_*"))

    v2.directory = Path(DUMMY_STATE_DIR)
    size = v2.calc_state_size()
    assert size > 0

    v3 = TriblerVersion(root_state_dir, "7.7")
    v3.directory.mkdir()
    v3.deleted = True
    v3.delete_state()
    assert v3.directory.exists()
    v3.deleted = False
    v3.delete_state()
    assert not v3.directory.exists()

    v4 = TriblerVersion(root_state_dir, "7.5.1a")
    v4.directory.mkdir()
    (v4.directory / 'triblerd.conf').write_text("abc")
    v5 = TriblerVersion(root_state_dir, "7.6.1b")
    v5.directory.mkdir()
    with pytest.raises(VersionError,
                       match='Directory for version 7.6.1b already exists'):
        v5.copy_state_from(v4)
    v5.copy_state_from(v4, overwrite=True)
    assert (v5.directory / 'triblerd.conf').read_text() == "abc"

    (root_state_dir /
     "version_history.json").write_text('{"last_version": "7.7"}')

    with pytest.raises(VersionError, match="Invalid history file structure"):
        VersionHistory(root_state_dir)

    (root_state_dir / "version_history.json").write_text(
        '{"last_version": "7.7", "history": {"1": "7.3.1a", "2": "7.7", "3": "7.5.1a", "4": "7.6.1b", "5": "7.8.1"}}'
    )

    (root_state_dir / "sqlite").mkdir()
    (root_state_dir / "channels").mkdir()
    (root_state_dir / 'triblerd.conf').write_text("abc")

    history = VersionHistory(root_state_dir)
    assert history.code_version.version_str == tribler_core.version.version_id
    assert repr(history) == "<VersionHistory[(7, 6), (7, 5), (7, 3)]>"

    dirs = history.get_disposable_state_directories()
    names = [d.name for d in dirs]
    assert len(names) == 5
    for name in names:
        assert name in ('7.5', '7.6', 'channels',
                        'sqlite') or name.startswith("deleted_v7.8_")

    remove_state_dirs(root_state_dir, names)
    assert not (root_state_dir / "7.5").exists()
    assert not (root_state_dir / "7.6").exists()
    assert not (root_state_dir / "channels").exists()
    assert not (root_state_dir / "sqlite").exists()
Esempio n. 12
0
def test_fork_state_directory(tmpdir_factory):
    # Scenario 1: the last used version has the same major/minor number as the code version, dir in place
    # no forking should happen, but version_history should be updated nonetheless
    tmpdir = tmpdir_factory.mktemp("scenario1")
    root_state_dir = Path(tmpdir)
    json_dict = {"last_version": "120.1.1", "history": dict()}
    json_dict["history"]["2"] = "120.1.1"
    state_dir = root_state_dir / "120.1"
    state_dir.mkdir()
    (root_state_dir / VERSION_HISTORY_FILENAME).write_text(
        json.dumps(json_dict))
    code_version_id = "120.1.2"

    history = VersionHistory(root_state_dir, code_version_id)
    assert history.last_run_version is not None
    assert history.last_run_version.directory == state_dir
    assert history.last_run_version != history.code_version
    assert history.code_version.directory == state_dir
    assert history.code_version.version_str != history.last_run_version.version_str
    assert not history.code_version.should_be_copied
    assert not history.code_version.should_recreate_directory

    forked_from = history.fork_state_directory_if_necessary()
    assert forked_from is None
    history_saved = history.save_if_necessary()
    assert history_saved

    history2 = VersionHistory(root_state_dir, code_version_id)
    assert history2.last_run_version == history2.code_version
    assert history2.last_run_version.version_str == code_version_id

    # Scenario 2: the last used version minor is lower than the code version, directory exists
    # normal upgrade scenario, dir should be forked and version_history should be updated
    tmpdir = tmpdir_factory.mktemp("scenario2")
    root_state_dir = Path(tmpdir)
    json_dict = {"last_version": "120.1.1", "history": dict()}
    json_dict["history"]["1"] = "120.1.1"
    state_dir = root_state_dir / "120.1"
    state_dir.mkdir()
    (root_state_dir / VERSION_HISTORY_FILENAME).write_text(
        json.dumps(json_dict))
    code_version_id = "120.3.2"

    history = VersionHistory(root_state_dir, code_version_id)
    assert history.last_run_version is not None
    assert history.last_run_version.directory == state_dir
    assert history.code_version != history.last_run_version
    assert history.code_version.directory != state_dir
    assert history.code_version.version_str != history.last_run_version.version_str
    assert history.code_version.should_be_copied
    assert not history.code_version.should_recreate_directory
    assert not history.code_version.directory.exists()

    forked_from = history.fork_state_directory_if_necessary()
    assert history.code_version.directory.exists()
    assert forked_from is not None and forked_from.version_str == "120.1.1"
    history_saved = history.save_if_necessary()
    assert history_saved

    history2 = VersionHistory(root_state_dir, code_version_id)
    assert history2.last_run_version == history2.code_version
    assert history2.last_run_version.version_str == code_version_id

    # Scenario 3: upgrade from 7.3 (unversioned dir)
    # dir should be forked and version_history should be created
    tmpdir = tmpdir_factory.mktemp("scenario3")
    root_state_dir = Path(tmpdir)
    (root_state_dir / "triblerd.conf").write_text("foo")  # 7.3 presence marker
    code_version_id = "120.3.2"

    history = VersionHistory(root_state_dir, code_version_id)
    assert history.last_run_version is not None
    assert history.last_run_version.directory == root_state_dir
    assert history.code_version != history.last_run_version
    assert history.code_version.directory != root_state_dir
    assert history.code_version.should_be_copied
    assert history.code_version.can_be_copied_from is not None
    assert history.code_version.can_be_copied_from.version_str == "7.3"
    assert not history.code_version.should_recreate_directory
    assert not history.code_version.directory.exists()

    forked_from = history.fork_state_directory_if_necessary()
    assert history.code_version.directory.exists()
    assert forked_from is not None and forked_from.version_str == "7.3"
    history_saved = history.save_if_necessary()
    assert history_saved

    history2 = VersionHistory(root_state_dir, code_version_id)
    assert history2.last_run_version == history2.code_version
    assert history2.last_run_version.version_str == code_version_id

    # Scenario 4: the user tried to upgrade to some tribler version, but failed. Now he tries again with
    # higher patch version of the same major/minor version.
    # The most recently used dir with major/minor version lower than the code version should be forked,
    # while the previous code version state directory should be renamed to a backup.
    tmpdir = tmpdir_factory.mktemp("scenario4")
    root_state_dir = Path(tmpdir)
    json_dict = {"last_version": "120.2.1", "history": dict()}
    # The user  was on 120.2
    json_dict["history"]["1"] = "120.2.0"
    state_dir_1 = root_state_dir / "120.2"
    state_dir_1.mkdir()

    # The user tried 120.3, they did not like it
    json_dict["history"]["2"] = "120.3.0"
    state_dir_2 = root_state_dir / "120.3"
    state_dir_2.mkdir()

    # The user returned to 120.2 and continued to use it
    json_dict["history"]["3"] = "120.2.1"
    (root_state_dir / VERSION_HISTORY_FILENAME).write_text(
        json.dumps(json_dict))

    # Now user tries 120.3.2 which has a higher patch version than his previous attempt at 120.3 series
    code_version_id = "120.3.2"

    history = VersionHistory(root_state_dir, code_version_id)
    assert history.last_run_version is not None
    assert history.last_run_version.directory == state_dir_1
    assert history.code_version != history.last_run_version
    assert history.code_version.directory != root_state_dir
    assert history.code_version.should_be_copied
    assert history.code_version.can_be_copied_from is not None
    assert history.code_version.can_be_copied_from.version_str == "120.2.1"
    assert history.code_version.directory.exists()
    assert history.code_version.should_recreate_directory

    forked_from = history.fork_state_directory_if_necessary()
    assert history.code_version.directory.exists()
    assert forked_from is not None and forked_from.version_str == "120.2.1"
    history_saved = history.save_if_necessary()
    assert history_saved
    # Check that the older 120.3 directory is not deleted, but instead renamed as a backup
    assert list(root_state_dir.glob("unused_v120.3_*"))

    history2 = VersionHistory(root_state_dir, code_version_id)
    assert history2.last_run_version == history2.code_version
    assert history2.last_run_version.version_str == code_version_id

    # Scenario 5: normal upgrade scenario, but from 7.4.x version (dir includes patch number)
    tmpdir = tmpdir_factory.mktemp("scenario5")
    root_state_dir = Path(tmpdir)
    json_dict = {"last_version": "7.4.4", "history": dict()}
    json_dict["history"]["2"] = "7.4.4"
    state_dir = root_state_dir / "7.4.4"
    state_dir.mkdir()
    (root_state_dir / VERSION_HISTORY_FILENAME).write_text(
        json.dumps(json_dict))

    code_version_id = "7.5.1"

    history = VersionHistory(root_state_dir, code_version_id)
    assert history.last_run_version is not None
    assert history.last_run_version.directory == state_dir
    assert history.code_version != history.last_run_version
    assert history.code_version.directory != root_state_dir
    assert history.code_version.should_be_copied
    assert history.code_version.can_be_copied_from is not None
    assert history.code_version.can_be_copied_from.version_str == "7.4.4"
    assert not history.code_version.directory.exists()
    assert not history.code_version.should_recreate_directory

    forked_from = history.fork_state_directory_if_necessary()
    assert history.code_version.directory.exists()
    assert forked_from is not None and forked_from.version_str == "7.4.4"
    history_saved = history.save_if_necessary()
    assert history_saved

    history2 = VersionHistory(root_state_dir, code_version_id)
    assert history2.last_run_version == history2.code_version
    assert history2.last_run_version.version_str == code_version_id
Esempio n. 13
0
    # Check whether we need to start the core or the user interface
    if parsed_args.core:
        from tribler_core.check_os import should_kill_other_tribler_instances

        should_kill_other_tribler_instances(root_state_dir)
        logger.info('Running Core' + ' in gui_test_mode' if parsed_args.gui_test_mode else '')
        load_logger_config('tribler-core', root_state_dir)

        # Check if we are already running a Tribler instance
        process_checker = ProcessChecker(root_state_dir)
        if process_checker.already_running:
            logger.info('Core is already running, exiting')
            sys.exit(1)
        process_checker.create_lock_file()
        version_history = VersionHistory(root_state_dir)
        state_dir = version_history.code_version.directory
        try:
            start_core.run_tribler_core(api_port, api_key, state_dir, gui_test_mode=parsed_args.gui_test_mode)
        finally:
            logger.info('Remove lock file')
            process_checker.remove_lock_file()

    else:
        from tribler_gui.utilities import get_translator

        logger.info('Running GUI' + ' in gui_test_mode' if parsed_args.gui_test_mode else '')

        # Workaround for macOS Big Sur, see https://github.com/Tribler/tribler/issues/5728
        if sys.platform == "darwin":
            logger.info('Enabling a workaround for macOS Big Sur')
Esempio n. 14
0
class CoreManager(QObject):
    """
    The CoreManager is responsible for managing the Tribler core (starting/stopping). When we are running the GUI tests,
    a fake API will be started.
    """

    tribler_stopped = pyqtSignal()
    core_state_update = pyqtSignal(str)

    def __init__(self, api_port, api_key, error_handler):
        QObject.__init__(self, None)

        self._logger = logging.getLogger(self.__class__.__name__)

        self.base_path = get_base_path()
        if not is_frozen():
            self.base_path = os.path.join(get_base_path(), "..")

        root_state_dir = get_root_state_directory()
        self.version_history = VersionHistory(root_state_dir)

        self.core_process = None
        self.api_port = api_port
        self.api_key = api_key
        self.events_manager = EventRequestManager(self.api_port, self.api_key,
                                                  error_handler)

        self.shutting_down = False
        self.should_stop_on_shutdown = False
        self.use_existing_core = True
        self.is_core_running = False
        self.core_traceback = None
        self.core_traceback_timestamp = 0

        self.check_state_timer = QTimer()
        self.check_state_timer.setSingleShot(True)
        connect(self.check_state_timer.timeout, self.check_core_ready)

    def on_core_read_ready(self):
        raw_output = bytes(self.core_process.readAll())
        decoded_output = raw_output.decode(errors="replace")
        if b'Traceback' in raw_output:
            self.core_traceback = decoded_output
            self.core_traceback_timestamp = int(round(time.time() * 1000))
        print(decoded_output.strip())  # noqa: T001

    def on_core_finished(self, exit_code, exit_status):
        if self.shutting_down and self.should_stop_on_shutdown:
            self.on_finished()
        elif not self.shutting_down and exit_code != 0:
            # Stop the event manager loop if it is running
            if self.events_manager.connect_timer and self.events_manager.connect_timer.isActive(
            ):
                self.events_manager.connect_timer.stop()

            exception_msg = (
                f"The Tribler core has unexpectedly finished "
                f"with exit code {exit_code} and status: {exit_status}!")
            if self.core_traceback:
                exception_msg += "\n\n%s\n(Timestamp: %d, traceback timestamp: %d)" % (
                    self.core_traceback,
                    int(round(time.time() * 1000)),
                    self.core_traceback_timestamp,
                )

            raise RuntimeError(exception_msg)

    def start(self, core_args=None, core_env=None):
        """
        First test whether we already have a Tribler process listening on port <CORE_API_PORT>.
        If so, use that one and don't start a new, fresh session.
        """
        def on_request_error(_):
            self.use_existing_core = False
            self.start_tribler_core(core_args=core_args, core_env=core_env)

        versions_to_delete = self.should_cleanup_old_versions()
        if versions_to_delete:
            for version in versions_to_delete:
                version.delete_state()

        # Connect to the events manager only after the cleanup is done
        self.events_manager.connect()
        connect(self.events_manager.reply.error, on_request_error)

        # Determine if we have notify the user to wait for the directory fork to finish
        if self.version_history.code_version.should_be_copied:
            # There is going to be a directory fork, so we extend the core connection timeout and notify the user
            self.events_manager.remaining_connection_attempts = 1200
            self.events_manager.change_loading_text.emit(
                "Copying data from previous Tribler version, please wait")

    def should_cleanup_old_versions(self) -> List[TriblerVersion]:
        # Skip old version check popup when running fake core, eg. during GUI tests
        # or during deployment tests since it blocks the tests with a popup dialog
        if START_FAKE_API or SKIP_VERSION_CLEANUP:
            return []

        if self.version_history.last_run_version == self.version_history.code_version:
            return []

        disposable_versions = self.version_history.get_disposable_versions(
            skip_versions=2)
        if not disposable_versions:
            return []

        storage_info = ""
        claimable_storage = 0
        for version in disposable_versions:
            state_size = version.calc_state_size()
            claimable_storage += state_size
            storage_info += f"{version.version_str} \t {format_size(state_size)}\n"

        # Show a question to the user asking if the user wants to remove the old data.
        title = "Delete state directories for old versions?"
        message_body = tr(
            "Press 'Yes' to remove state directories for older versions of Tribler "
            "and reclaim %s of storage space. "
            "Tribler used those directories during upgrades from previous versions. "
            "Now those directories can be safely deleted. \n\n"
            "If unsure, press 'No'. "
            "You will be able to remove those directories from the Settings->Data page later."
        ) % format_size(claimable_storage)

        user_choice = self._show_question_box(title,
                                              message_body,
                                              storage_info,
                                              default_button=QMessageBox.Yes)
        if user_choice == QMessageBox.Yes:
            return disposable_versions
        return []

    def _show_question_box(self,
                           title,
                           body,
                           additional_text,
                           default_button=None):
        message_box = QMessageBox()
        message_box.setIcon(QMessageBox.Question)
        message_box.setWindowTitle(title)
        message_box.setText(body)
        message_box.setInformativeText(additional_text)
        message_box.setStandardButtons(QMessageBox.No | QMessageBox.Yes)
        if default_button:
            message_box.setDefaultButton(default_button)
        return message_box.exec_()

    def start_tribler_core(self, core_args=None, core_env=None):
        if not START_FAKE_API:
            if not core_env:
                core_env = QProcessEnvironment.systemEnvironment()
                core_env.insert("CORE_PROCESS", "1")
                core_env.insert("CORE_BASE_PATH", self.base_path)
                core_env.insert("CORE_API_PORT", f"{self.api_port}")
                core_env.insert("CORE_API_KEY", self.api_key.decode('utf-8'))
            if not core_args:
                core_args = sys.argv

            self.core_process = QProcess()
            self.core_process.setProcessEnvironment(core_env)
            self.core_process.setReadChannel(QProcess.StandardOutput)
            self.core_process.setProcessChannelMode(QProcess.MergedChannels)
            connect(self.core_process.readyRead, self.on_core_read_ready)
            connect(self.core_process.finished, self.on_core_finished)
            self.core_process.start(sys.executable, core_args)

        self.check_core_ready()

    def check_core_ready(self):
        TriblerNetworkRequest("state",
                              self.on_received_state,
                              capture_core_errors=False,
                              priority=QNetworkRequest.HighPriority)

    def on_received_state(self, state):
        if not state or 'state' not in state or state['state'] not in [
                'STARTED', 'EXCEPTION'
        ]:
            self.check_state_timer.start(50)
            return

        self.core_state_update.emit(state['readable_state'])

        if state['state'] == 'STARTED':
            self.events_manager.connect(reschedule_on_err=False)
            self.is_core_running = True
        elif state['state'] == 'EXCEPTION':
            raise RuntimeError(state['last_exception'])

    def stop(self, stop_app_on_shutdown=True):
        if self.core_process or self.is_core_running:
            self.events_manager.shutting_down = True
            TriblerNetworkRequest("shutdown",
                                  lambda _: None,
                                  method="PUT",
                                  priority=QNetworkRequest.HighPriority)

            if stop_app_on_shutdown:
                self.should_stop_on_shutdown = True

    def on_finished(self):
        self.tribler_stopped.emit()
        if self.shutting_down:
            QApplication.quit()