Skip to content

Commit

Permalink
Merge pull request #7383 from drew2a/fix/7369
Browse files Browse the repository at this point in the history
Check on ancient version
  • Loading branch information
drew2a authored Apr 25, 2023
2 parents be6747c + 7b37f47 commit 620de76
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 25 deletions.
1 change: 1 addition & 0 deletions src/tribler/core/upgrade/config_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ def convert_config_to_tribler76(state_dir):
"""
Convert the download config files from Tribler 7.5 to 7.6 format.
"""
logger.info('Upgrade config to 7.6')
config = ConfigObj(infile=(str(state_dir / 'triblerd.conf')), default_encoding='utf-8')
if 'http_api' in config:
logger.info('Convert config')
Expand Down
45 changes: 45 additions & 0 deletions src/tribler/core/upgrade/tests/test_triblerversion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from tribler.core.upgrade.version_manager import TriblerVersion


def test_create_from_version(tmp_path):
# Test that we can create a TriblerVersion object from a version string
v = TriblerVersion(tmp_path, '7.13.1')
assert v.version.version == [7, 13, 1]


def test_equal(tmp_path):
# Test correctness of equal comparison
def v(s):
return TriblerVersion(tmp_path, s).version

assert v('7.13.1') == v('7.13.1')
assert v('7.13.1') != v('7.13.2')


def test_greater(tmp_path):
# Test correctness of greater than comparison
def v(s):
return TriblerVersion(tmp_path, s).version

assert v('7.13.1') >= v('7.13.1')
assert v('7.13.1') > v('7.13')
assert v('7.13.1') > v('7.12')


def test_less(tmp_path):
# Test correctness of less than comparison
def v(s):
return TriblerVersion(tmp_path, s).version

assert v('7.13.1') <= v('7.13.1')
assert v('7.13') < v('7.13.1')
assert v('7.12') < v('7.13.1')


def test_is_ancient(tmp_path):
# Test that we can correctly determine whether a version is ancient
last_supported = '7.5'
assert not TriblerVersion(tmp_path, '7.13').is_ancient(last_supported)
assert not TriblerVersion(tmp_path, '7.5').is_ancient(last_supported)

assert TriblerVersion(tmp_path, '7.4').is_ancient(last_supported)
9 changes: 9 additions & 0 deletions src/tribler/core/upgrade/upgrade.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,12 @@ def remove_old_logs(self) -> Tuple[List[Path], List[Path]]:
return removed_files, left_files

def upgrade_tags_to_knowledge(self):
self._logger.info('Upgrade tags to knowledge')
migration = MigrationTagsToKnowledge(self.state_dir, self.secondary_key)
migration.run()

def upgrade_pony_db_14to15(self):
self._logger.info('Upgrade Pony DB from version 14 to version 15')
mds_path = self.state_dir / STATEDIR_DB_DIR / 'metadata.db'

mds = MetadataStore(mds_path, self.channels_dir, self.primary_key, disable_sync=True,
Expand All @@ -141,6 +143,7 @@ def upgrade_pony_db_14to15(self):
mds.shutdown()

def upgrade_pony_db_13to14(self):
self._logger.info('Upgrade Pony DB from version 13 to version 14')
mds_path = self.state_dir / STATEDIR_DB_DIR / 'metadata.db'
tagdb_path = self.state_dir / STATEDIR_DB_DIR / 'tags.db'

Expand All @@ -160,6 +163,7 @@ def upgrade_pony_db_12to13(self):
Upgrade GigaChannel DB from version 12 (7.9.x) to version 13 (7.11.x).
Version 12 adds index for TorrentState.last_check attribute.
"""
self._logger.info('Upgrade Pony DB 12 to 13')
# We have to create the Metadata Store object because Session-managed Store has not been started yet
database_path = self.state_dir / STATEDIR_DB_DIR / 'metadata.db'
if database_path.exists():
Expand All @@ -174,6 +178,7 @@ def upgrade_pony_db_11to12(self):
Version 12 adds a `json_text`, `binary_data` and `data_type` fields
to TorrentState table if it already does not exist.
"""
self._logger.info('Upgrade Pony DB 11 to 12')
# We have to create the Metadata Store object because Session-managed Store has not been started yet
database_path = self.state_dir / STATEDIR_DB_DIR / 'metadata.db'
if not database_path.exists():
Expand All @@ -189,10 +194,12 @@ def upgrade_pony_db_10to11(self):
Version 11 adds a `self_checked` field to TorrentState table if it
already does not exist.
"""
self._logger.info('Upgrade Pony DB 10 to 11')
# We have to create the Metadata Store object because Session-managed Store has not been started yet
database_path = self.state_dir / STATEDIR_DB_DIR / 'metadata.db'
if not database_path.exists():
return
# code of the migration
mds = MetadataStore(database_path, self.channels_dir, self.primary_key,
disable_sync=True, check_tables=False, db_version=10)
self.do_upgrade_pony_db_10to11(mds)
Expand All @@ -204,6 +211,7 @@ def upgrade_bw_accounting_db_8to9(self):
Specifically, this upgrade wipes all transactions and addresses an issue where payouts with the wrong amount
were made. Also see https://github.com/Tribler/tribler/issues/5789.
"""
self._logger.info('Upgrade bandwidth accounting DB 8 to 9')
to_version = 9

database_path = self.state_dir / STATEDIR_DB_DIR / 'bandwidth.db'
Expand Down Expand Up @@ -367,6 +375,7 @@ def upgrade_pony_db_8to10(self):
Upgrade GigaChannel DB from version 8 (7.5.x) to version 10 (7.6.x).
This will recreate the database anew, which can take quite some time.
"""
self._logger.info('Upgrading GigaChannel DB from version 8 to 10')
database_path = self.state_dir / STATEDIR_DB_DIR / 'metadata.db'
if not database_path.exists() or get_db_version(database_path) >= 10:
# Either no old db exists, or the old db version is up to date - nothing to do
Expand Down
23 changes: 13 additions & 10 deletions src/tribler/core/upgrade/version_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,14 @@ class TriblerVersion:
should_recreate_directory: bool
deleted: bool

def __init__(self, root_state_dir: Path, version_str: str, files_to_copy: List[str],
def __init__(self, root_state_dir: Path, version_str: str, files_to_copy: List[str] = None,
last_launched_at: Optional[float] = None):
if last_launched_at is None:
last_launched_at = time.time()
self.logger = logging.getLogger(self.__class__.__name__)
self.version_str = version_str
self.version_tuple = tuple(LooseVersion(version_str).version)
self.major_minor = self.version_tuple[:2]
self.version = LooseVersion(version_str)
self.major_minor = tuple(self.version.version[:2])
self.last_launched_at = last_launched_at
self.root_state_dir = root_state_dir
self.directory = self.get_directory()
Expand All @@ -90,7 +90,7 @@ def __init__(self, root_state_dir: Path, version_str: str, files_to_copy: List[s
self.should_be_copied = False
self.should_recreate_directory = False
self.deleted = False
self.files_to_copy = files_to_copy
self.files_to_copy = files_to_copy or []

def __repr__(self):
return f'<{self.__class__.__name__}{{{self.version_str}}}>'
Expand Down Expand Up @@ -187,6 +187,9 @@ def rename_directory(self, prefix='unused_v'):
self.logger.info(f"Rename state directory for version {self.version_str} to {dirname}")
return self.directory.rename(self.root_state_dir / dirname)

def is_ancient(self, last_supported_version: str):
return self.version < LooseVersion(last_supported_version)


class VersionHistory:
"""
Expand All @@ -201,7 +204,7 @@ class VersionHistory:
versions_by_number: List[TriblerVersion]
versions_by_time: List[TriblerVersion]
last_run_version: Optional[TriblerVersion]
code_version: TriblerVersion
code_version: TriblerVersion # current Tribler's version

# pylint: disable=too-many-branches
def __init__(self, root_state_dir: Path, code_version_id: Optional[str] = None):
Expand All @@ -226,15 +229,14 @@ def __init__(self, root_state_dir: Path, code_version_id: Optional[str] = None):
versions_by_time[i].prev_version_by_time = versions_by_time[i + 1]

code_version = TriblerVersion(root_state_dir, code_version_id, self.files_to_copy)
self.logger.info(f"Current Tribler version is {code_version.version_str}")

if not last_run_version:
# No previous versions found
self.logger.info(f"No previous version found, current Tribler version is {code_version.version_str}")
self.logger.info("No previous version found")
elif last_run_version.version_str == code_version.version_str:
# Previously we started the same version, nothing to upgrade
code_version = last_run_version
self.logger.info(
f"The previously started version is the same as the current one: {code_version.version_str}")
self.logger.info("The previously started version is the same as the current one")
elif last_run_version.major_minor == code_version.major_minor:
# Previously we started version from the same directory and can continue use this directory
self.logger.info(f"The previous version {last_run_version.version_str} "
Expand Down Expand Up @@ -333,9 +335,9 @@ def save(self):

def fork_state_directory_if_necessary(self) -> Optional[TriblerVersion]:
"""Returns version string from which the state directory was forked"""
self.logger.info('Fork state directory')
code_version = self.code_version
if code_version.should_recreate_directory:
self.logger.info('State directory should be recreated')
code_version.rename_directory()

if code_version.should_be_copied:
Expand All @@ -344,6 +346,7 @@ def fork_state_directory_if_necessary(self) -> Optional[TriblerVersion]:
if prev_version: # should always be True here
code_version.copy_state_from(prev_version)
return prev_version
self.logger.info('State directory should not be copied')
return None

def get_installed_versions(self, with_code_version=True) -> List[TriblerVersion]:
Expand Down
59 changes: 44 additions & 15 deletions src/tribler/gui/upgrade_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from typing import List, Optional, TYPE_CHECKING

from PyQt5.QtCore import QObject, QThread, pyqtSignal
from PyQt5.QtWidgets import QMessageBox
from PyQt5.QtWidgets import QApplication, QMessageBox

from tribler.core.components.key.key_component import KeyComponent
from tribler.core.config.tribler_config import TriblerConfig
Expand All @@ -27,6 +27,7 @@ class StateDirUpgradeWorker(QObject):

def __init__(self, version_history: VersionHistory):
super().__init__()
self.logger = logging.getLogger(self.__class__.__name__)
self.version_history = version_history
self._upgrade_interrupted = False
connect(self.stop_upgrade, self._stop_upgrade)
Expand All @@ -42,21 +43,22 @@ def _update_status_callback(self, text):

def run(self):
try:
self.logger.info('Run')
self.upgrade_state_dir(
self.version_history,
update_status_callback=self._update_status_callback,
interrupt_upgrade_event=self.upgrade_interrupted,
)
except Exception as exc: # pylint: disable=broad-except
self.logger.exception(exc)
self.finished.emit(exc)
else:
self.logger.info('Finished')
self.finished.emit(None)

@staticmethod
def upgrade_state_dir(version_history: VersionHistory,
update_status_callback=None,
def upgrade_state_dir(self, version_history: VersionHistory, update_status_callback=None,
interrupt_upgrade_event=None):
logging.info('Upgrade state dir')
self.logger.info(f'Upgrade state dir for {version_history}')
# 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.fork_state_directory_if_necessary()
Expand Down Expand Up @@ -88,11 +90,12 @@ class UpgradeManager(QObject):
upgrader_tick = pyqtSignal(str)
upgrader_finished = pyqtSignal()

def __init__(self, version_history: VersionHistory):
def __init__(self, version_history: VersionHistory, last_supported_version: str = '7.5'):
QObject.__init__(self, None)

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

self.last_supported_version = last_supported_version
self.version_history = version_history
self.new_version_dialog_postponed: bool = False
self.dialog: Optional[ConfirmationDialog] = None
Expand Down Expand Up @@ -130,25 +133,27 @@ def on_button_clicked(click_result: int):
connect(self.dialog.button_clicked, on_button_clicked)
self.dialog.show()

def _show_question_box(self, title, body, additional_text, default_button=None):
@staticmethod
def _show_message_box(title, body, icon, standard_buttons, default_button, additional_text=''):
message_box = QMessageBox()
message_box.setIcon(QMessageBox.Question)
message_box.setIcon(icon)
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)
message_box.setStandardButtons(standard_buttons)
message_box.setDefaultButton(default_button)
return message_box.exec_()

def should_cleanup_old_versions(self) -> List[TriblerVersion]:
self._logger.info('Getting old versions...')
self._logger.info('Should cleanup old versions')

if self.version_history.last_run_version == self.version_history.code_version:
self._logger.info('Last run version is the same as the current version. Exit cleanup procedure.')
return []

disposable_versions = self.version_history.get_disposable_versions(skip_versions=2)
if not disposable_versions:
self._logger.info('No disposable versions. Exit cleanup procedure.')
return []

storage_info = ""
Expand All @@ -157,9 +162,9 @@ def should_cleanup_old_versions(self) -> List[TriblerVersion]:
state_size = version.calc_state_size()
claimable_storage += state_size
storage_info += f"{version.version_str} \t {format_size(state_size)}\n"

self._logger.info(f'Storage info: {storage_info}')
# Show a question to the user asking if the user wants to remove the old data.
title = "Delete state directories for old versions?"
title = tr("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. "
Expand All @@ -169,13 +174,37 @@ def should_cleanup_old_versions(self) -> List[TriblerVersion]:
"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)
user_choice = self._show_message_box(
title,
message_body,
additional_text=storage_info,
icon=QMessageBox.Question,
standard_buttons=QMessageBox.No | QMessageBox.Yes,
default_button=QMessageBox.Yes
)
if user_choice == QMessageBox.Yes:
self._logger.info('User decided to delete old versions. Start cleanup procedure.')
return disposable_versions
return []

def start(self):
self._logger.info('Start upgrade process')
last_version = self.version_history.last_run_version
if last_version and last_version.is_ancient(self.last_supported_version):
self._logger.info('Ancient version detected. Quitting Tribler.')
self._show_message_box(
tr("Ancient version detected"),
body=tr("You are running an old version of Tribler. "
"It is not possible to upgrade from this version to the most recent one."
"Please do upgrade incrementally (download Tribler 7.10, upgrade, "
"then download the most recent one, upgrade)."),
icon=QMessageBox.Warning,
standard_buttons=QMessageBox.Yes,
default_button=QMessageBox.Yes
)
QApplication.quit()
return

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

0 comments on commit 620de76

Please sign in to comment.