Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove the torrent grouping based on names #7984

Closed
4 changes: 4 additions & 0 deletions src/run_tribler.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@

from tribler.core.sentry_reporter.sentry_reporter import SentryReporter, SentryStrategy
from tribler.core.sentry_reporter.sentry_scrubber import SentryScrubber
from tribler.core.utilities import linecache_patch
from tribler.core.utilities.asyncio_fixes.finish_accept_patch import apply_finish_accept_patch
from tribler.core.utilities.asyncio_fixes.proactor_recvfrom_patch import apply_proactor_recvfrom_patch
from tribler.core.utilities.slow_coro_detection.main_thread_stack_tracking import start_main_thread_stack_tracing
from tribler.core.utilities.osutils import get_root_state_directory
from tribler.core.utilities.utilities import is_frozen
Expand Down Expand Up @@ -78,6 +80,7 @@ def init_boot_logger():


def main():
linecache_patch.patch()
init_boot_logger()

parsed_args = RunTriblerArgsParser().parse_args()
Expand All @@ -95,6 +98,7 @@ def main():
if parsed_args.core:
if sys.platform == 'win32':
apply_finish_accept_patch()
apply_proactor_recvfrom_patch()

from tribler.core.utilities.pony_utils import track_slow_db_sessions
track_slow_db_sessions()
Expand Down
22 changes: 22 additions & 0 deletions src/tribler/core/sentry_reporter/sentry_reporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from typing import Any, Dict, List, Optional

import sentry_sdk
import sentry_sdk.utils
from faker import Faker
from sentry_sdk.integrations.logging import LoggingIntegration, ignore_logger
from sentry_sdk.integrations.threading import ThreadingIntegration
Expand All @@ -22,11 +23,31 @@
parse_stacktrace,
)


def fix_sentry_logger(sentry_logger):
# Sentry log requires reconfiguration to be useful. By default, Sentry does not show even error-level log records
# until the debug option for Sentry is enabled. That means that unsuccessful attempts to send report are ignored.
# Enabling debug for Sentry is not recommended in production, and it sends the debug messages to stderr via
# a separate handler that is not in line with how Tribler handles logs. As a solution, Sentry developers recommend
# manual logger reconfiguration: https://github.com/getsentry/sentry-python/issues/1191#issuecomment-1023721841

for f in list(sentry_logger.filters):
sentry_logger.removeFilter(f)

for h in list(sentry_logger.handlers):
sentry_logger.removeHandler(h)

sentry_logger.setLevel(logging.WARNING)


fix_sentry_logger(sentry_sdk.utils.logger)

VALUE = 'value'
TYPE = 'type'
LAST_CORE_OUTPUT = 'last_core_output'
LAST_PROCESSES = 'last_processes'
PLATFORM = 'platform'
PROCESS_ARCHITECTURE = 'process_architecture'
OS = 'os'
MACHINE = 'machine'
COMMENTS = 'comments'
Expand Down Expand Up @@ -213,6 +234,7 @@ def send_event(self, event: Dict = None, post_data: Dict = None, sys_info: Dict

# tags
tags = event[TAGS]
tags[PROCESS_ARCHITECTURE] = get_value(post_data, PROCESS_ARCHITECTURE)
tags[VERSION] = get_value(post_data, VERSION)
tags[MACHINE] = get_value(post_data, MACHINE)
tags[OS] = get_value(post_data, OS)
Expand Down
9 changes: 7 additions & 2 deletions src/tribler/core/sentry_reporter/sentry_scrubber.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,17 +172,22 @@ def scrub_entity_recursively(self, entity: Union[str, Dict, List, Any], depth=10
if isinstance(entity, dict):
result = {}
for key, value in entity.items():
if key in self.dict_keys_for_scrub:
if key in self.dict_keys_for_scrub and isinstance(value, str):
value = value.strip()
fake_value = obfuscate_string(value)
placeholder = self.create_placeholder(fake_value)
self.add_sensitive_pair(value, placeholder)
result[key] = self.scrub_entity_recursively(value, depth)
result[key] = placeholder
else:
result[key] = self.scrub_entity_recursively(value, depth)
return result

return entity

def add_sensitive_pair(self, text, placeholder):
if not (text and text.strip()): # We should not replace empty substrings in the middle of other strings
return

if text in self.sensitive_occurrences:
return

Expand Down
3 changes: 0 additions & 3 deletions src/tribler/core/sentry_reporter/sentry_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,6 @@ def obfuscate_string(s: str, part_of_speech: str = 'noun') -> str:

The same random words will be generated for the same given strings.
"""
if not s:
return s

faker = Faker(locale='en_US')
faker.seed_instance(s)
return faker.word(part_of_speech=part_of_speech)
23 changes: 14 additions & 9 deletions src/tribler/core/sentry_reporter/tests/test_sentry_scrubber.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,16 +213,16 @@ def test_scrub_event(scrubber):
}
assert scrubber.scrub_event(event) == {
'the very first item': '<highlight>',
'server_name': '<protection>',
'server_name': '<kid>',
CONTEXTS: {
REPORTER: {
'any': {
'USERNAME': '<father>',
'USERNAME': '<conference>',
'USERDOMAIN_ROAMINGPROFILE': '<protection>',
'PATH': '/users/<highlight>/apps',
'TMP_WIN': 'C:\\Users\\<restaurant>\\AppData\\Local\\Temp',
'USERDOMAIN': '<marriage>',
'COMPUTERNAME': '<message>',
'USERDOMAIN': '<tune>',
'COMPUTERNAME': '<lady>',
},
STACKTRACE: [
'Traceback (most recent call last):',
Expand Down Expand Up @@ -301,15 +301,20 @@ def test_scrub_dict(scrubber):
assert scrubber.scrub_entity_recursively(None) is None
assert scrubber.scrub_entity_recursively({}) == {}

given = {'PATH': '/home/username/some/', 'USERDOMAIN': 'UD', 'USERNAME': 'U', 'REPEATED': 'user username UD U'}
assert scrubber.scrub_entity_recursively({'key': [1]}) == {'key': [1]} # non-string values should not lead to error

given = {'PATH': '/home/username/some/', 'USERDOMAIN': 'UD', 'USERNAME': 'U', 'REPEATED': 'user username UD U',
'key': ''}
assert scrubber.scrub_entity_recursively(given) == {'PATH': '/home/<highlight>/some/',
'REPEATED': 'user <highlight> <school> <night>',
'USERDOMAIN': '<school>',
'USERNAME': '<night>'}
'USERNAME': '<night>',
'key': '<dress>'}

assert 'username' in scrubber.sensitive_occurrences.keys()
assert 'UD' in scrubber.sensitive_occurrences.keys()
assert 'U' in scrubber.sensitive_occurrences.keys()
assert 'username' in scrubber.sensitive_occurrences
assert 'UD' in scrubber.sensitive_occurrences
assert 'U' in scrubber.sensitive_occurrences
assert '' not in scrubber.sensitive_occurrences


def test_scrub_list(scrubber):
Expand Down
3 changes: 1 addition & 2 deletions src/tribler/core/sentry_reporter/tests/test_sentry_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,7 @@ def test_extract_dict():


OBFUSCATED_STRINGS = [
(None, None),
('', ''),
('', 'dress'),
('any', 'challenge'),
('string', 'quality'),
]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from asyncio.log import logger

try:
import _overlapped
except ImportError:
_overlapped = None


NULL = 0

ERROR_PORT_UNREACHABLE = 1234 # _overlapped.ERROR_PORT_UNREACHABLE, available in Python >= 3.11
ERROR_NETNAME_DELETED = 64
ERROR_OPERATION_ABORTED = 995

patch_applied = False


def apply_proactor_recvfrom_patch(): # pragma: no cover
global patch_applied # pylint: disable=global-statement
if patch_applied:
return

from asyncio import IocpProactor

IocpProactor.recvfrom = patched_recvfrom

patch_applied = True
logger.info("Patched IocpProactor.recvfrom to handle ERROR_PORT_UNREACHABLE")


def patched_recvfrom(self, conn, nbytes, flags=0):
self._register_with_iocp(conn)
ov = _overlapped.Overlapped(NULL)
try:
ov.WSARecvFrom(conn.fileno(), nbytes, flags)
except BrokenPipeError:
return self._result((b'', None))

def finish_recvfrom(trans, key, ov, error_class=OSError):
try:
return ov.getresult()
except error_class as exc:
if exc.winerror in (ERROR_NETNAME_DELETED, ERROR_OPERATION_ABORTED):
raise ConnectionResetError(*exc.args)

# ******************** START OF THE PATCH ********************
# WSARecvFrom will report ERROR_PORT_UNREACHABLE when the same
# socket was used to send to an address that is not listening.
if exc.winerror == ERROR_PORT_UNREACHABLE:
return b'', None # ignore the error
# ******************** END OF THE PATCH **********************

raise

return self._register(ov, conn, finish_recvfrom)
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from unittest.mock import Mock, patch

import pytest

from tribler.core.utilities.asyncio_fixes.proactor_recvfrom_patch import ERROR_NETNAME_DELETED, ERROR_OPERATION_ABORTED, \
ERROR_PORT_UNREACHABLE, patched_recvfrom


@patch('tribler.core.utilities.asyncio_fixes.proactor_recvfrom_patch._overlapped')
def test_patched_recvfrom_broken_pipe_error(overlapped):
proactor, conn, nbytes, flags, ov = (Mock() for _ in range(5))
overlapped.Overlapped.return_value = ov
conn.fileno.return_value = Mock()
ov.WSARecvFrom.side_effect = BrokenPipeError()
proactor._result.return_value = Mock()

result = patched_recvfrom(proactor, conn, nbytes, flags)

proactor._register_with_iocp.assert_called_with(conn)
overlapped.Overlapped.assert_called_with(0)
ov.WSARecvFrom.assert_called_with(conn.fileno.return_value, nbytes, flags)
proactor._result.assert_called_with((b'', None))
assert result is proactor._result.return_value


@patch('tribler.core.utilities.asyncio_fixes.proactor_recvfrom_patch._overlapped')
def test_patched_recvfrom(overlapped):
proactor, conn, nbytes, flags, ov, trans, key = (Mock() for _ in range(7))
overlapped.Overlapped.return_value = ov
conn.fileno.return_value = Mock()
proactor._register.return_value = Mock()

result = patched_recvfrom(proactor, conn, nbytes, flags)
proactor._register.assert_called_once()
assert result is proactor._register.return_value
args = proactor._register.call_args.args
assert args[:2] == (ov, conn) and len(args) == 3

finish_recvfrom = args[2]

class OSErrorMock(Exception):
def __init__(self, winerror):
self.winerror = winerror

with patch('tribler.core.utilities.asyncio_fixes.proactor_recvfrom_patch.OSError', 'OSErrorMock'):

# Should raise ConnectionResetError if ov.getresult() raises OSError with winerror=ERROR_NETNAME_DELETED

ov.getresult.assert_not_called()
ov.getresult.side_effect = OSErrorMock(ERROR_NETNAME_DELETED)
with pytest.raises(ConnectionResetError):
finish_recvfrom(trans, key, ov, error_class=OSErrorMock)

# Should raise ConnectionResetError if ov.getresult() raises OSError with winerror=ERROR_OPERATION_ABORTED

ov.getresult.side_effect = OSErrorMock(ERROR_OPERATION_ABORTED)
with pytest.raises(ConnectionResetError):
finish_recvfrom(trans, key, ov, error_class=OSErrorMock)

# Should return empty result if ov.getresult() raises OSError with winerror=ERROR_PORT_UNREACHABLE

ov.getresult.side_effect = OSErrorMock(ERROR_PORT_UNREACHABLE)
result = finish_recvfrom(trans, key, ov, error_class=OSErrorMock)
assert result == (b'', None)

# Should reraise any other OSError raised by ov.getresult()

ov.getresult.side_effect = OSErrorMock(-1)
with pytest.raises(OSErrorMock):
finish_recvfrom(trans, key, ov, error_class=OSErrorMock)

# Should return result of ov.getresult() if no exceptions arised

ov.getresult.side_effect = None
ov.getresult.return_value = Mock()
result = finish_recvfrom(trans, key, ov)
assert result is ov.getresult.return_value

assert ov.getresult.call_count == 5
25 changes: 25 additions & 0 deletions src/tribler/core/utilities/linecache_patch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import linecache
import sys

original_updatecache = linecache.updatecache


def patched_updatecache(filename, *args, **kwargs):
if getattr(sys, 'frozen', False):
# When Tribler runs from a bundle, Tribler sources are available inside the `tribler_source` subfolder
if filename.startswith('src\\tribler'): # Relative path with cx_freeze on Windows
filename = 'tribler_source' + filename[3:] # Replacing `src\\` -> `tribler_source\\`
elif filename.startswith('tribler/'): # Relative path with PyInstaller on Mac/Linux:
filename = 'tribler_source/' + filename # Appending `tribler_source/` to the relative path
result = original_updatecache(filename, *args, **kwargs)
return result


patched_updatecache.patched = True


def patch():
if getattr(linecache.updatecache, 'patched', False):
return

linecache.updatecache = patched_updatecache
36 changes: 36 additions & 0 deletions src/tribler/core/utilities/tests/test_linecache_patch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from unittest.mock import Mock, patch

from tribler.core.utilities import linecache_patch


def original_updatecache_mock(filename, *args, **kwargs):
return [filename] # as if file consist of a single line that equal to the filename


@patch('tribler.core.utilities.linecache_patch.original_updatecache', original_updatecache_mock)
@patch('sys.frozen', False, create=True)
def test_not_frozen():
assert linecache_patch.patched_updatecache('src\\tribler\\path') == ['src\\tribler\\path']
assert linecache_patch.patched_updatecache('tribler/path') == ['tribler/path']
assert linecache_patch.patched_updatecache('other/path') == ['other/path']


@patch('tribler.core.utilities.linecache_patch.original_updatecache', original_updatecache_mock)
@patch('sys.frozen', True, create=True)
def test_frozen():
assert linecache_patch.patched_updatecache('src\\tribler\\path') == ['tribler_source\\tribler\\path']
assert linecache_patch.patched_updatecache('tribler/path') == ['tribler_source/tribler/path']
assert linecache_patch.patched_updatecache('other/path') == ['other/path']


@patch('tribler.core.utilities.linecache_patch.linecache')
def test_patch(linecache_mock):
_original_updatecache_mock = Mock(patched=True)
linecache_mock.updatecache = _original_updatecache_mock

linecache_patch.patch()
assert linecache_mock.updatecache is _original_updatecache_mock # already patched, no second time patch

_original_updatecache_mock.patched = False
linecache_patch.patch()
assert linecache_mock.updatecache is linecache_patch.patched_updatecache
3 changes: 2 additions & 1 deletion src/tribler/gui/dialogs/feedbackdialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from PyQt5.QtWidgets import QAction, QDialog, QMessageBox, QTreeWidgetItem

from tribler.core.components.reporter.reported_error import ReportedError
from tribler.core.sentry_reporter.sentry_reporter import SentryReporter
from tribler.core.sentry_reporter.sentry_reporter import PROCESS_ARCHITECTURE, SentryReporter
from tribler.core.sentry_reporter.sentry_scrubber import SentryScrubber
from tribler.core.sentry_reporter.sentry_tools import CONTEXT_DELIMITER, LONG_TEXT_DELIMITER
from tribler.gui.sentry_mixin import AddBreadcrumbOnShowMixin
Expand Down Expand Up @@ -142,6 +142,7 @@ def on_send_clicked(self, checked):
stack = self.error_text_edit.toPlainText()

post_data = {
PROCESS_ARCHITECTURE: platform.architecture()[0],
"version": self.tribler_version,
"machine": platform.machine(),
"os": platform.platform(),
Expand Down
5 changes: 0 additions & 5 deletions src/tribler/gui/widgets/tablecontentdelegate.py
Original file line number Diff line number Diff line change
Expand Up @@ -379,11 +379,6 @@ def draw_title_and_tags(self, painter: QPainter, option: QStyleOptionViewItem, i
debug = False # change to True to see the search rank of items and to highlight remote items
item_name = data_item["name"]

group = data_item.get("group")
if group:
has_remote_items = any(group_item.get('remote') for group_item in group.values())
item_name += f" (+ {len(group)} similar{' *' if debug and has_remote_items else ''})"

if debug:
rank = data_item.get("rank")
if rank is not None:
Expand Down
Loading
Loading