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

Fix command line arguments and detect duplicate sessions #8089

Merged
merged 7 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 47 additions & 16 deletions src/run_tribler.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import pystray
import tribler
from aiohttp import ClientSession
from PIL import Image
from tribler.core.session import Session
from tribler.tribler_config import TriblerConfigManager
Expand Down Expand Up @@ -50,6 +51,19 @@ def get_root_state_directory(requested_path: os.PathLike | None) -> Path:
return root_state_dir


async def start_download(config: TriblerConfigManager, server_url: str, torrent_uri: str) -> None:
"""
Start a download by calling the REST API.
"""
async with ClientSession() as client, client.put(server_url + "/api/downloads",
headers={"X-Api-Key": config.get("api/key")},
json={"uri": torrent_uri}) as response:
if response.status == 200:
logger.info("Successfully started torrent %s", torrent_uri)
else:
logger.warning("Failed to start torrent %s: %s", torrent_uri, await response.text())


async def main() -> None:
"""
The main script entry point.
Expand All @@ -60,38 +74,55 @@ async def main() -> None:

root_state_dir = get_root_state_directory(os.environ.get('TSTATEDIR', 'state_directory'))
logger.info("Root state dir: %s", root_state_dir)

api_port, api_key = int(os.environ.get('CORE_API_PORT', '0')), os.environ.get('CORE_API_KEY')

config = TriblerConfigManager(root_state_dir / "configuration.json")
config.set("state_dir", str(root_state_dir))

if config.get("api/refresh_port_on_start"):
config.set("api/http_port", 0)
config.set("api/https_port", 0)

if api_key is None and config.get("api/key") is None:
api_key = os.urandom(16).hex()

if api_key is not None and api_key != config.get("api/key"):
config.set("api/key", api_key)
if "CORE_API_PORT" in os.environ:
config.set("api/http_port", int(os.environ.get("CORE_API_PORT")))
config.write()

if api_port is not None and api_port != config.get("api/http_port"):
config.set("api/http_port", api_port)
if "CORE_API_KEY" in os.environ:
config.set("api/key", os.environ.get("CORE_API_KEY"))
config.write()

logger.info("Start tribler core. API port: %d. API key: %s.", api_port, config.get("api/key"))
if config.get("api/key") is None:
config.set("api/key", os.urandom(16).hex())
config.write()

logger.info("Creating session. API port: %d. API key: %s.", config.get("api/http_port"), config.get("api/key"))
session = Session(config)

torrent_uri = parsed_args.get('torrent')
if torrent_uri and os.path.exists(torrent_uri):
if torrent_uri.endswith(".torrent"):
torrent_uri = Path(torrent_uri).as_uri()
if torrent_uri.endswith(".magnet"):
torrent_uri = Path(torrent_uri).read_text()
server_url = await session.find_api_server()

if server_url:
logger.info("Core already running at %s", server_url)
if torrent_uri:
logger.info("Starting torrent using existing core")
await start_download(config, server_url, torrent_uri)
webbrowser.open_new_tab(server_url + f"?key={config.get('api/key')}")
logger.info("Shutting down")
return

await session.start()

server_url = await session.find_api_server()
if server_url and torrent_uri:
await start_download(config, server_url, torrent_uri)

image_path = Path(tribler.__file__).parent / "ui/public/tribler.png"
image = Image.open(image_path.resolve())
url = f"http://localhost:{session.rest_manager.get_api_port()}/ui/#/downloads/all?key={config.get('api/key')}"
api_port = session.rest_manager.get_api_port()
url = f"http://{config.get('api/http_host')}:{api_port}/ui/#/downloads/all?key={config.get('api/key')}"
menu = (pystray.MenuItem('Open', lambda: webbrowser.open_new_tab(url)),
pystray.MenuItem('Quit', lambda: session.shutdown_event.set()))
icon = pystray.Icon("Tribler", icon=image, title="Tribler", menu=menu)
webbrowser.open_new_tab(url)
threading.Thread(target=icon.run).start()

await session.shutdown_event.wait()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,10 @@ def from_defaults(settings: TriblerConfigManager) -> DownloadConfig:
defaults.validate(Validator())
config = DownloadConfig(defaults)

config.set_hops(int(settings.get("libtorrent/download_defaults/number_hops")))
if settings.get("libtorrent/download_defaults/anonymity_enabled"):
config.set_hops(int(settings.get("libtorrent/download_defaults/number_hops")))
else:
config.set_hops(0)
config.set_safe_seeding(settings.get("libtorrent/download_defaults/safeseeding_enabled"))
config.set_dest_dir(settings.get("libtorrent/download_defaults/saveas"))

Expand Down
12 changes: 6 additions & 6 deletions src/tribler/core/libtorrent/restapi/downloads_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,14 @@ def create_dconfig_from_params(self, parameters: dict) -> tuple[DownloadConfig,
"""
download_config = DownloadConfig.from_defaults(self.download_manager.config)

anon_hops = parameters.get('anon_hops', 0)
anon_hops = parameters.get('anon_hops')
safe_seeding = bool(parameters.get('safe_seeding', 0))

if anon_hops > 0 and not safe_seeding:
return None, "Cannot set anonymous download without safe seeding enabled"

if anon_hops >= 0:
download_config.set_hops(anon_hops)
if anon_hops is not None:
if anon_hops > 0 and not safe_seeding:
return None, "Cannot set anonymous download without safe seeding enabled"
if anon_hops >= 0:
download_config.set_hops(anon_hops)

if safe_seeding:
download_config.set_safe_seeding(True)
Expand Down
8 changes: 8 additions & 0 deletions src/tribler/core/restapi/rest_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,10 @@ async def start_http_site(self, runner: web.AppRunner) -> None:
str(e))
raise

current_port = api_port or self.site._server.sockets[0].getsockname()[1] # noqa: SLF001
self.config.set("api/http_port_running", current_port)
self.config.write()

self._logger.info("HTTP REST API server started on port %d", self.get_api_port())

async def start_https_site(self, runner: web.AppRunner) -> None:
Expand All @@ -257,6 +261,10 @@ async def start_https_site(self, runner: web.AppRunner) -> None:
await self.site_https.start()
self._logger.info("Started HTTPS REST API: %s", self.site_https.name)

current_port = port or self.site_https._server.sockets[0].getsockname()[1] # noqa: SLF001
self.config.set("api/https_port_running", current_port)
self.config.write()

async def stop(self) -> None:
"""
Clean up all the REST endpoints and connections.
Expand Down
27 changes: 27 additions & 0 deletions src/tribler/core/session.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from __future__ import annotations

import asyncio
import logging
from asyncio import Event
from contextlib import contextmanager
from typing import TYPE_CHECKING, Generator

import aiohttp
from ipv8.loader import IPv8CommunityLoader
from ipv8_service import IPv8

Expand Down Expand Up @@ -74,6 +76,15 @@ def rust_enhancements(session: Session) -> Generator[None, None, None]:
if_specs[i]["worker_threads"] = previous_value


async def _is_url_available(url: str, timeout: int=1) -> bool:
async with aiohttp.ClientSession() as session:
try:
async with session.get(url, timeout=timeout):
return True
except asyncio.TimeoutError:
return False


class Session:
"""
A session manager that manages all components.
Expand Down Expand Up @@ -159,6 +170,22 @@ async def start(self) -> None:
if self.config.get("statistics"):
self.rest_manager.get_endpoint("/api/ipv8").endpoints["/overlays"].enable_overlay_statistics(True, None, True)

async def find_api_server(self) -> str | None:
"""
Find the API server, if available.
"""
if port := self.config.get("api/http_port_running"):
http_url = f'http://{self.config.get("api/http_host")}:{port}'
if await _is_url_available(http_url):
return http_url

if port := self.config.get("api/https_port_running"):
https_url = f'https://{self.config.get("api/https_host")}:{port}'
if await _is_url_available(https_url):
return https_url

return None

async def shutdown(self) -> None:
"""
Shut down all connections and components.
Expand Down
5 changes: 3 additions & 2 deletions src/tribler/tribler_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ class ApiConfig(TypedDict):
https_enabled: bool
https_host: str
https_port: int
refresh_port_on_start: bool


class ContentDiscoveryCommunityConfig(TypedDict):
Expand Down Expand Up @@ -166,7 +165,9 @@ class TriblerConfig(TypedDict):
"https_host": "127.0.0.1",
"https_port": 0,
"https_certfile": "https_certfile",
"refresh_port_on_start": True
# Ports currently in-use. Used by run_tribler.py to detect duplicate sessions.
"http_port_running": 0,
"https_port_running": 0,
},

"ipv8": ipv8_default_config,
Expand Down
3 changes: 2 additions & 1 deletion src/tribler/ui/public/locales/en_US.json
Original file line number Diff line number Diff line change
Expand Up @@ -123,5 +123,6 @@
"Socks5": "Socks5",
"Socks5Auth": "Socks5 with authentication",
"HTTP": "HTTP",
"HTTPAuth": "HTTP with authentication"
"HTTPAuth": "HTTP with authentication",
"WebServerSettings": "Web server settings"
}
52 changes: 26 additions & 26 deletions src/tribler/ui/public/locales/es_ES.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@
"Infohash": "Información del hash",
"Downloads": "Descargas",
"AddTorrent": "Añadir torrent",
"All": "TODO",
"Downloading": "DESCARGANDO",
"Completed": "COMPLETADO",
"Active": "ACTIVO",
"Inactive": "INACTIVO",
"All": "Todo",
"Downloading": "Descargando",
"Completed": "Completado",
"Active": "Activo",
"Inactive": "Inactivo",
"Popular": "Populares",
"Settings": "Configuración",
"General": "GENERAL",
"Connection": "CONEXIÓN",
"Bandwidth": "ANCHO DE BANDA",
"Seeding": "SEMBRADO",
"Anonymity": "ANONIMATO",
"General": "General",
"Connection": "Conexión",
"Bandwidth": "Ancho de banda",
"Seeding": "Sembrado",
"Anonymity": "Anonimato",
"Debug": "Depurar",
"ImportTorrentFile": "Importar torrent desde archivo",
"ImportTorrentURL": "Importar un torrent desde un magnet/URL",
Expand Down Expand Up @@ -59,16 +59,15 @@
"Size": "Tamaño",
"Created": "Creado",
"Status": "Estado",
"Status": "ESTADO",
"Seeds": "SEMILLAS",
"Peers": "PARES",
"SpeedDown": "VELOCIDAD (BAJADA)",
"SpeedUp": "VELOCIDAD (SUBIDA)",
"Ratio": "RATIO",
"Anonymous": "¿ANÓNIMOS?",
"Hops": "SALTOS",
"ETA": "TIEMPO",
"AddedOn": "AÑADIDO EL",
"Seeds": "Semillas",
"Peers": "Pares",
"SpeedDown": "Velocidad (bajada)",
"SpeedUp": "Velocidad (subida)",
"Ratio": "Ratio",
"Anonymous": "¿Anónimos?",
"Hops": "Saltos",
"ETA": "Tiempo",
"AddedOn": "Añadido el",
"ForceRecheck": "Forzar nueva verificación",
"ExportTorrent": "Exportar archivo torrent",
"MoveStorage": "Establecer destino",
Expand All @@ -87,10 +86,10 @@
"Availability": "Disponibilidad",
"Files": "Archivos",
"Trackers": "Rastreadores",
"PeerIpPort": "PARES (IP/PUERTO)",
"Completed": "COMPLETADO",
"Flags": "BANDERAS",
"Client": "CLIENTE",
"PeerIpPort": "Pares (IP/puerto)",
"Completed": "Completado",
"Flags": "Banderas",
"Client": "Cliente",
"SeedersLeechers": "{{seeders, number}} sembradores, {{leechers, number}} recolectores",
"NoResults": "No hay resultados.",
"GotoFirst": "Ir a la primera página",
Expand Down Expand Up @@ -118,11 +117,12 @@
"ChangeStorageLocation": "Ubicación",
"ChangeStorageButton": "Mover almacenamiento",
"Actions": "Comportamiento",
"CreateTorrentButton": "CREAR TORRENT",
"CreateTorrentButton": "Crear torrent",
"None": "Ninguno",
"Socks4": "Socks4",
"Socks5": "Socks5",
"Socks5Auth": "Socks5 con autenticación",
"HTTP": "HTTP",
"HTTPAuth": "HTTP con autenticación"
"HTTPAuth": "HTTP con autenticación",
"WebServerSettings": "Configurações do servidor web"
}
Loading