Skip to content

Commit

Permalink
Merge pull request #6738 from drew2a/fix/6700
Browse files Browse the repository at this point in the history
Fix/6700
  • Loading branch information
drew2a authored Jan 24, 2022
2 parents 4905494 + be5b4d8 commit 71bb956
Show file tree
Hide file tree
Showing 23 changed files with 405 additions and 239 deletions.
119 changes: 10 additions & 109 deletions src/run_tribler.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,15 @@
# A fix for "LookupError: unknown encoding: idna" error.
# Adding encodings.idna to hiddenimports is not enough.
# https://github.com/pyinstaller/pyinstaller/issues/1113
# noinspection PyUnresolvedReferences
import encodings.idna # pylint: disable=unused-import
import logging.config
import os
import sys

from tribler_common.logger import load_logger_config
from tribler_common.process_checker import ProcessChecker
from tribler_common.sentry_reporter.sentry_reporter import SentryStrategy
from tribler_common.sentry_reporter.sentry_scrubber import SentryScrubber
from tribler_common.version_manager import VersionHistory

from tribler_core import start_core
from tribler_core.components.reporter.exception_handler import default_core_exception_handler

logger = logging.getLogger(__name__)
Expand All @@ -25,6 +23,7 @@ class RunTriblerArgsParser(argparse.ArgumentParser):
def __init__(self, *args, **kwargs):
kwargs['description'] = 'Run Tribler BitTorrent client'
super().__init__(*args, **kwargs)
self.add_argument('torrent', help='torrent file to download', default='', nargs='?')
self.add_argument('--core', action="store_true")
self.add_argument('--gui-test-mode', action="store_true")

Expand Down Expand Up @@ -65,6 +64,7 @@ def init_boot_logger():
init_sentry_reporter()

parsed_args = RunTriblerArgsParser().parse_args()
logger.info(f'Run Tribler: {parsed_args}')

# Get root state directory (e.g. from environment variable or from system default)
from tribler_common.osutils import get_root_state_directory
Expand All @@ -77,109 +77,10 @@ def init_boot_logger():

# 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()
from tribler_core.start_core import run_core

else:
from PyQt5.QtCore import QSettings
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')
os.environ["QT_MAC_WANTS_LAYER"] = "1"

# Set up logging
load_logger_config('tribler-gui', root_state_dir)

from tribler_core.check_os import (
check_and_enable_code_tracing,
check_environment,
check_free_space,
enable_fault_handler,
error_and_exit,
)
from tribler_core.exceptions import TriblerException

try:
# Enable tracer using commandline args: --trace-debug or --trace-exceptions
trace_logger = check_and_enable_code_tracing('gui', root_state_dir)

enable_fault_handler(root_state_dir)

# Exit if we cant read/write files, etc.
check_environment()

check_free_space()

from tribler_gui.tribler_app import TriblerApplication
from tribler_gui.tribler_window import TriblerWindow

app_name = os.environ.get('TRIBLER_APP_NAME', 'triblerapp')
app = TriblerApplication(app_name, sys.argv)

# ACHTUNG! translator MUST BE created and assigned to a separate variable
# BEFORE calling installTranslator on app. Otherwise, it won't work for some reason
settings = QSettings('nl.tudelft.tribler')
translator = get_translator(settings.value('translation', None))
app.installTranslator(translator)

if app.is_running():
logger.info('Application is running')
for arg in sys.argv[1:]:
if os.path.exists(arg) and arg.endswith(".torrent"):
app.send_message(f"file:{arg}")
elif arg.startswith('magnet'):
app.send_message(arg)

sys.exit(1)

logger.info('Start Tribler Window')
window = TriblerWindow(settings,
root_state_dir,
api_port=api_port,
api_key=api_key,
run_core=True)
window.setWindowTitle("Tribler")
app.set_activation_window(window)
app.parse_sys_args(sys.argv)
sys.exit(app.exec_())

except ImportError as ie:
logger.exception(ie)
error_and_exit("Import Error", f"Import error: {ie}")

except TriblerException as te:
logger.exception(te)
error_and_exit("Tribler Exception", f"{te}")

except SystemExit:
logger.info("Shutting down Tribler")
if trace_logger:
trace_logger.close()

# Flush all the logs to make sure it is written to file before it exits
for handler in logging.getLogger().handlers:
handler.flush()

default_core_exception_handler.sentry_reporter.global_strategy = SentryStrategy.SEND_SUPPRESSED
raise
run_core(api_port, api_key, root_state_dir, parsed_args)
else: # GUI
from tribler_gui.start_gui import run_gui

run_gui(api_port, api_key, root_state_dir, parsed_args)
45 changes: 45 additions & 0 deletions src/tribler-common/tribler_common/rest_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import os
from typing import Any, Union

from yarl import URL

MAGNET_SCHEME = 'magnet'
FILE_SCHEME = 'file'
HTTP_SCHEME = 'http'
HTTPS_SCHEME = 'https'


def path_to_uri(file_path: Union[str, Any]) -> str:
"""Convert path to url
Example:
'/path/to/file' -> 'file:///path/to/file'
"""
if not isinstance(file_path, str):
file_path = str(file_path)
return str(URL().build(scheme=FILE_SCHEME, path=file_path))


def uri_to_path(file_uri: str) -> str:
"""Convert uri to path
Example:
'file:///path/to/file' -> '/path/to/file'
"""
path = URL(file_uri).path
if os.name == 'nt':
# Removes first slash for win OS
# see https://github.com/aio-libs/yarl/issues/674
return path.lstrip('/')
return path


def scheme_from_uri(uri: str) -> str:
"""Get scheme from URI
Examples:
'file:///some/file' -> 'file'
'magnet:link' -> 'magnet'
'http://en.wikipedia.org' -> 'http'
"""
return URL(uri).scheme
53 changes: 53 additions & 0 deletions src/tribler-common/tribler_common/tests/test_rest_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from unittest.mock import patch

import pytest

from tribler_common.rest_utils import path_to_uri, scheme_from_uri, uri_to_path

NIX_PATHS = [
('/path/to/file', 'file:///path/to/file'),
('/path/to/file with space', 'file:///path/to/file%20with%20space'),
('/path/to/%20%21file', 'file:///path/to/%2520%2521file'), # See: https://github.com/Tribler/tribler/issues/6700
]

WIN_PATHS = [
('C:\\path\\to\\file', 'file:///C:%5Cpath%5Cto%5Cfile'),
('C:\\path\\to\\file with space', 'file:///C:%5Cpath%5Cto%5Cfile%20with%20space'),
('C:\\path\\to\\%20%21file', 'file:///C:%5Cpath%5Cto%5C%2520%2521file'),
]

SCHEMES = [
('file:///path/to/file', 'file'),
('magnet:link', 'magnet'),
('http://en.wikipedia.org', 'http'),
]

# posix
@pytest.mark.parametrize('path, uri', NIX_PATHS)
@patch('os.name', 'posix')
def test_path_to_uri(path, uri):
assert path_to_uri(path) == uri


@pytest.mark.parametrize('path, uri', NIX_PATHS)
@patch('os.name', 'posix')
def test_uri_to_path(path, uri):
assert uri_to_path(uri) == path


# win
@pytest.mark.parametrize('path, uri', WIN_PATHS)
@patch('os.name', 'nt')
def test_path_to_uri_win(path, uri):
assert path_to_uri(path) == uri


@pytest.mark.parametrize('path, uri', WIN_PATHS)
@patch('os.name', 'nt')
def test_uri_to_path_win(path, uri):
assert uri_to_path(uri) == path


@pytest.mark.parametrize('path, scheme', SCHEMES)
def test_scheme_from_uri(path, scheme):
assert scheme_from_uri(path) == scheme
8 changes: 1 addition & 7 deletions src/tribler-common/tribler_common/tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
from pathlib import Path
from unittest.mock import MagicMock, patch

from tribler_common.patch_import import patch_import
from tribler_common.utilities import Query, extract_tags, parse_query, show_system_popup, to_fts_query, uri_to_path
from tribler_common.utilities import Query, extract_tags, parse_query, show_system_popup, to_fts_query

# pylint: disable=import-outside-toplevel, import-error
# fmt: off

def test_uri_to_path():
path = Path(__file__).parent / "bla%20foo.bar"
uri = path.as_uri()
assert uri_to_path(uri) == path


def test_to_fts_query():
assert to_fts_query(None) is None
Expand Down
11 changes: 0 additions & 11 deletions src/tribler-common/tribler_common/utilities.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
import itertools
import os
import platform
import re
import sys
from dataclasses import dataclass, field
from typing import Set, Tuple
from urllib.parse import urlparse
from urllib.request import url2pathname

from tribler_core.utilities.path_util import Path


def is_frozen():
Expand All @@ -23,12 +18,6 @@ def is_frozen():
return True


def uri_to_path(uri):
parsed = urlparse(uri)
host = "{0}{0}{mnt}{0}".format(os.path.sep, mnt=parsed.netloc)
return Path(host) / url2pathname(parsed.path)


fts_query_re = re.compile(r'\w+', re.UNICODE)
tags_re = re.compile(r'#[^\s^#]{3,50}(?=[#\s]|$)')

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,15 @@
from ipv8.taskmanager import TaskManager, task

from tribler_common.network_utils import default_network_utils
from tribler_common.rest_utils import (
FILE_SCHEME,
HTTPS_SCHEME,
HTTP_SCHEME,
MAGNET_SCHEME,
scheme_from_uri,
uri_to_path,
)
from tribler_common.simpledefs import DLSTATUS_SEEDING, MAX_LIBTORRENT_RATE_LIMIT, NTFY, STATEDIR_CHECKPOINT_DIR
from tribler_common.utilities import uri_to_path

from tribler_core.components.libtorrent.download_manager.dht_health_manager import DHTHealthManager
from tribler_core.components.libtorrent.download_manager.download import Download
Expand Down Expand Up @@ -505,10 +512,12 @@ def _map_call_on_ltsessions(self, hops, funcname, *args, **kwargs):
getattr(self.get_session(hops), funcname)(*args, **kwargs)

async def start_download_from_uri(self, uri, config=None):
if uri.startswith("http"):
scheme = scheme_from_uri(uri)

if scheme in (HTTP_SCHEME, HTTPS_SCHEME):
tdef = await TorrentDef.load_from_url(uri)
return self.start_download(tdef=tdef, config=config)
if uri.startswith("magnet:"):
if scheme == MAGNET_SCHEME:
name, infohash, _ = parse_magnetlink(uri)
if infohash is None:
raise RuntimeError("Missing infohash")
Expand All @@ -517,9 +526,9 @@ async def start_download_from_uri(self, uri, config=None):
else:
tdef = TorrentDefNoMetainfo(infohash, "Unknown name" if name is None else name, url=uri)
return self.start_download(tdef=tdef, config=config)
if uri.startswith("file:"):
argument = uri_to_path(uri)
return self.start_download(torrent_file=argument, config=config)
if scheme == FILE_SCHEME:
file = uri_to_path(uri)
return self.start_download(torrent_file=file, config=config)
raise Exception("invalid uri")

def start_download(self, torrent_file=None, tdef=None, config=None, checkpoint_disabled=False, hidden=False):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from tribler_common.rest_utils import path_to_uri

from tribler_core.components.base import Component
from tribler_core.components.key.key_component import KeyComponent
from tribler_core.components.libtorrent.download_manager.download_manager import DownloadManager
Expand Down Expand Up @@ -33,7 +35,7 @@ async def run(self):

if config.gui_test_mode:
from tribler_core.tests.tools.common import TORRENT_WITH_DIRS # pylint: disable=import-outside-toplevel
uri = f"file:{TORRENT_WITH_DIRS}"
uri = path_to_uri(TORRENT_WITH_DIRS)
await self.download_manager.start_download_from_uri(uri)

async def shutdown(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,9 +233,10 @@ def get_files_info_json(download):
"and should only be used in situations where this data is required. "
)
async def get_downloads(self, request):
get_peers = request.query.get('get_peers', '0') == '1'
get_pieces = request.query.get('get_pieces', '0') == '1'
get_files = request.query.get('get_files', '0') == '1'
params = request.query
get_peers = params.get('get_peers', '0') == '1'
get_pieces = params.get('get_pieces', '0') == '1'
get_files = params.get('get_files', '0') == '1'

downloads_json = []
downloads = self.download_manager.get_downloads()
Expand Down Expand Up @@ -373,16 +374,17 @@ async def get_downloads(self, request):
'location, a magnet link or a HTTP(S) url.'),
}))
async def add_download(self, request):
parameters = await request.json()
if not parameters.get('uri'):
params = await request.json()
uri = params.get('uri')
if not uri:
return RESTResponse({"error": "uri parameter missing"}, status=HTTP_BAD_REQUEST)

download_config, error = DownloadsEndpoint.create_dconfig_from_params(parameters)
download_config, error = DownloadsEndpoint.create_dconfig_from_params(params)
if error:
return RESTResponse({"error": error}, status=HTTP_BAD_REQUEST)

try:
download = await self.download_manager.start_download_from_uri(parameters['uri'], config=download_config)
download = await self.download_manager.start_download_from_uri(uri, config=download_config)
except Exception as e:
return RESTResponse({"error": str(e)}, status=HTTP_INTERNAL_SERVER_ERROR)

Expand Down
Loading

0 comments on commit 71bb956

Please sign in to comment.