Skip to content

Commit fd56b6c

Browse files
authored
Fix command line arguments and detect duplicate sessions (#8089)
2 parents 3240c60 + f740f77 commit fd56b6c

File tree

18 files changed

+217
-113
lines changed

18 files changed

+217
-113
lines changed

src/run_tribler.py

+47-16
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
import pystray
1515
import tribler
16+
from aiohttp import ClientSession
1617
from PIL import Image
1718
from tribler.core.session import Session
1819
from tribler.tribler_config import TriblerConfigManager
@@ -50,6 +51,19 @@ def get_root_state_directory(requested_path: os.PathLike | None) -> Path:
5051
return root_state_dir
5152

5253

54+
async def start_download(config: TriblerConfigManager, server_url: str, torrent_uri: str) -> None:
55+
"""
56+
Start a download by calling the REST API.
57+
"""
58+
async with ClientSession() as client, client.put(server_url + "/api/downloads",
59+
headers={"X-Api-Key": config.get("api/key")},
60+
json={"uri": torrent_uri}) as response:
61+
if response.status == 200:
62+
logger.info("Successfully started torrent %s", torrent_uri)
63+
else:
64+
logger.warning("Failed to start torrent %s: %s", torrent_uri, await response.text())
65+
66+
5367
async def main() -> None:
5468
"""
5569
The main script entry point.
@@ -60,38 +74,55 @@ async def main() -> None:
6074

6175
root_state_dir = get_root_state_directory(os.environ.get('TSTATEDIR', 'state_directory'))
6276
logger.info("Root state dir: %s", root_state_dir)
63-
64-
api_port, api_key = int(os.environ.get('CORE_API_PORT', '0')), os.environ.get('CORE_API_KEY')
65-
6677
config = TriblerConfigManager(root_state_dir / "configuration.json")
6778
config.set("state_dir", str(root_state_dir))
6879

69-
if config.get("api/refresh_port_on_start"):
70-
config.set("api/http_port", 0)
71-
config.set("api/https_port", 0)
72-
73-
if api_key is None and config.get("api/key") is None:
74-
api_key = os.urandom(16).hex()
75-
76-
if api_key is not None and api_key != config.get("api/key"):
77-
config.set("api/key", api_key)
80+
if "CORE_API_PORT" in os.environ:
81+
config.set("api/http_port", int(os.environ.get("CORE_API_PORT")))
7882
config.write()
7983

80-
if api_port is not None and api_port != config.get("api/http_port"):
81-
config.set("api/http_port", api_port)
84+
if "CORE_API_KEY" in os.environ:
85+
config.set("api/key", os.environ.get("CORE_API_KEY"))
8286
config.write()
8387

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

92+
logger.info("Creating session. API port: %d. API key: %s.", config.get("api/http_port"), config.get("api/key"))
8693
session = Session(config)
94+
95+
torrent_uri = parsed_args.get('torrent')
96+
if torrent_uri and os.path.exists(torrent_uri):
97+
if torrent_uri.endswith(".torrent"):
98+
torrent_uri = Path(torrent_uri).as_uri()
99+
if torrent_uri.endswith(".magnet"):
100+
torrent_uri = Path(torrent_uri).read_text()
101+
server_url = await session.find_api_server()
102+
103+
if server_url:
104+
logger.info("Core already running at %s", server_url)
105+
if torrent_uri:
106+
logger.info("Starting torrent using existing core")
107+
await start_download(config, server_url, torrent_uri)
108+
webbrowser.open_new_tab(server_url + f"?key={config.get('api/key')}")
109+
logger.info("Shutting down")
110+
return
111+
87112
await session.start()
88113

114+
server_url = await session.find_api_server()
115+
if server_url and torrent_uri:
116+
await start_download(config, server_url, torrent_uri)
117+
89118
image_path = Path(tribler.__file__).parent / "ui/public/tribler.png"
90119
image = Image.open(image_path.resolve())
91-
url = f"http://localhost:{session.rest_manager.get_api_port()}/ui/#/downloads/all?key={config.get('api/key')}"
120+
api_port = session.rest_manager.get_api_port()
121+
url = f"http://{config.get('api/http_host')}:{api_port}/ui/#/downloads/all?key={config.get('api/key')}"
92122
menu = (pystray.MenuItem('Open', lambda: webbrowser.open_new_tab(url)),
93123
pystray.MenuItem('Quit', lambda: session.shutdown_event.set()))
94124
icon = pystray.Icon("Tribler", icon=image, title="Tribler", menu=menu)
125+
webbrowser.open_new_tab(url)
95126
threading.Thread(target=icon.run).start()
96127

97128
await session.shutdown_event.wait()

src/tribler/core/libtorrent/download_manager/download_config.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,10 @@ def from_defaults(settings: TriblerConfigManager) -> DownloadConfig:
133133
defaults.validate(Validator())
134134
config = DownloadConfig(defaults)
135135

136-
config.set_hops(int(settings.get("libtorrent/download_defaults/number_hops")))
136+
if settings.get("libtorrent/download_defaults/anonymity_enabled"):
137+
config.set_hops(int(settings.get("libtorrent/download_defaults/number_hops")))
138+
else:
139+
config.set_hops(0)
137140
config.set_safe_seeding(settings.get("libtorrent/download_defaults/safeseeding_enabled"))
138141
config.set_dest_dir(settings.get("libtorrent/download_defaults/saveas"))
139142

src/tribler/core/libtorrent/restapi/downloads_endpoint.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -101,14 +101,14 @@ def create_dconfig_from_params(self, parameters: dict) -> tuple[DownloadConfig,
101101
"""
102102
download_config = DownloadConfig.from_defaults(self.download_manager.config)
103103

104-
anon_hops = parameters.get('anon_hops', 0)
104+
anon_hops = parameters.get('anon_hops')
105105
safe_seeding = bool(parameters.get('safe_seeding', 0))
106106

107-
if anon_hops > 0 and not safe_seeding:
108-
return None, "Cannot set anonymous download without safe seeding enabled"
109-
110-
if anon_hops >= 0:
111-
download_config.set_hops(anon_hops)
107+
if anon_hops is not None:
108+
if anon_hops > 0 and not safe_seeding:
109+
return None, "Cannot set anonymous download without safe seeding enabled"
110+
if anon_hops >= 0:
111+
download_config.set_hops(anon_hops)
112112

113113
if safe_seeding:
114114
download_config.set_safe_seeding(True)

src/tribler/core/restapi/rest_manager.py

+8
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,10 @@ async def start_http_site(self, runner: web.AppRunner) -> None:
242242
str(e))
243243
raise
244244

245+
current_port = api_port or self.site._server.sockets[0].getsockname()[1] # noqa: SLF001
246+
self.config.set("api/http_port_running", current_port)
247+
self.config.write()
248+
245249
self._logger.info("HTTP REST API server started on port %d", self.get_api_port())
246250

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

264+
current_port = port or self.site_https._server.sockets[0].getsockname()[1] # noqa: SLF001
265+
self.config.set("api/https_port_running", current_port)
266+
self.config.write()
267+
260268
async def stop(self) -> None:
261269
"""
262270
Clean up all the REST endpoints and connections.

src/tribler/core/session.py

+27
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
from __future__ import annotations
22

3+
import asyncio
34
import logging
45
from asyncio import Event
56
from contextlib import contextmanager
67
from typing import TYPE_CHECKING, Generator
78

9+
import aiohttp
810
from ipv8.loader import IPv8CommunityLoader
911
from ipv8_service import IPv8
1012

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

7678

79+
async def _is_url_available(url: str, timeout: int=1) -> bool:
80+
async with aiohttp.ClientSession() as session:
81+
try:
82+
async with session.get(url, timeout=timeout):
83+
return True
84+
except asyncio.TimeoutError:
85+
return False
86+
87+
7788
class Session:
7889
"""
7990
A session manager that manages all components.
@@ -159,6 +170,22 @@ async def start(self) -> None:
159170
if self.config.get("statistics"):
160171
self.rest_manager.get_endpoint("/api/ipv8").endpoints["/overlays"].enable_overlay_statistics(True, None, True)
161172

173+
async def find_api_server(self) -> str | None:
174+
"""
175+
Find the API server, if available.
176+
"""
177+
if port := self.config.get("api/http_port_running"):
178+
http_url = f'http://{self.config.get("api/http_host")}:{port}'
179+
if await _is_url_available(http_url):
180+
return http_url
181+
182+
if port := self.config.get("api/https_port_running"):
183+
https_url = f'https://{self.config.get("api/https_host")}:{port}'
184+
if await _is_url_available(https_url):
185+
return https_url
186+
187+
return None
188+
162189
async def shutdown(self) -> None:
163190
"""
164191
Shut down all connections and components.

src/tribler/tribler_config.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ class ApiConfig(TypedDict):
2424
https_enabled: bool
2525
https_host: str
2626
https_port: int
27-
refresh_port_on_start: bool
2827

2928

3029
class ContentDiscoveryCommunityConfig(TypedDict):
@@ -166,7 +165,9 @@ class TriblerConfig(TypedDict):
166165
"https_host": "127.0.0.1",
167166
"https_port": 0,
168167
"https_certfile": "https_certfile",
169-
"refresh_port_on_start": True
168+
# Ports currently in-use. Used by run_tribler.py to detect duplicate sessions.
169+
"http_port_running": 0,
170+
"https_port_running": 0,
170171
},
171172

172173
"ipv8": ipv8_default_config,

src/tribler/ui/public/locales/en_US.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -123,5 +123,6 @@
123123
"Socks5": "Socks5",
124124
"Socks5Auth": "Socks5 with authentication",
125125
"HTTP": "HTTP",
126-
"HTTPAuth": "HTTP with authentication"
126+
"HTTPAuth": "HTTP with authentication",
127+
"WebServerSettings": "Web server settings"
127128
}

src/tribler/ui/public/locales/es_ES.json

+26-26
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,18 @@
33
"Infohash": "Información del hash",
44
"Downloads": "Descargas",
55
"AddTorrent": "Añadir torrent",
6-
"All": "TODO",
7-
"Downloading": "DESCARGANDO",
8-
"Completed": "COMPLETADO",
9-
"Active": "ACTIVO",
10-
"Inactive": "INACTIVO",
6+
"All": "Todo",
7+
"Downloading": "Descargando",
8+
"Completed": "Completado",
9+
"Active": "Activo",
10+
"Inactive": "Inactivo",
1111
"Popular": "Populares",
1212
"Settings": "Configuración",
13-
"General": "GENERAL",
14-
"Connection": "CONEXIÓN",
15-
"Bandwidth": "ANCHO DE BANDA",
16-
"Seeding": "SEMBRADO",
17-
"Anonymity": "ANONIMATO",
13+
"General": "General",
14+
"Connection": "Conexión",
15+
"Bandwidth": "Ancho de banda",
16+
"Seeding": "Sembrado",
17+
"Anonymity": "Anonimato",
1818
"Debug": "Depurar",
1919
"ImportTorrentFile": "Importar torrent desde archivo",
2020
"ImportTorrentURL": "Importar un torrent desde un magnet/URL",
@@ -59,16 +59,15 @@
5959
"Size": "Tamaño",
6060
"Created": "Creado",
6161
"Status": "Estado",
62-
"Status": "ESTADO",
63-
"Seeds": "SEMILLAS",
64-
"Peers": "PARES",
65-
"SpeedDown": "VELOCIDAD (BAJADA)",
66-
"SpeedUp": "VELOCIDAD (SUBIDA)",
67-
"Ratio": "RATIO",
68-
"Anonymous": "¿ANÓNIMOS?",
69-
"Hops": "SALTOS",
70-
"ETA": "TIEMPO",
71-
"AddedOn": "AÑADIDO EL",
62+
"Seeds": "Semillas",
63+
"Peers": "Pares",
64+
"SpeedDown": "Velocidad (bajada)",
65+
"SpeedUp": "Velocidad (subida)",
66+
"Ratio": "Ratio",
67+
"Anonymous": "¿Anónimos?",
68+
"Hops": "Saltos",
69+
"ETA": "Tiempo",
70+
"AddedOn": "Añadido el",
7271
"ForceRecheck": "Forzar nueva verificación",
7372
"ExportTorrent": "Exportar archivo torrent",
7473
"MoveStorage": "Establecer destino",
@@ -87,10 +86,10 @@
8786
"Availability": "Disponibilidad",
8887
"Files": "Archivos",
8988
"Trackers": "Rastreadores",
90-
"PeerIpPort": "PARES (IP/PUERTO)",
91-
"Completed": "COMPLETADO",
92-
"Flags": "BANDERAS",
93-
"Client": "CLIENTE",
89+
"PeerIpPort": "Pares (IP/puerto)",
90+
"Completed": "Completado",
91+
"Flags": "Banderas",
92+
"Client": "Cliente",
9493
"SeedersLeechers": "{{seeders, number}} sembradores, {{leechers, number}} recolectores",
9594
"NoResults": "No hay resultados.",
9695
"GotoFirst": "Ir a la primera página",
@@ -118,11 +117,12 @@
118117
"ChangeStorageLocation": "Ubicación",
119118
"ChangeStorageButton": "Mover almacenamiento",
120119
"Actions": "Comportamiento",
121-
"CreateTorrentButton": "CREAR TORRENT",
120+
"CreateTorrentButton": "Crear torrent",
122121
"None": "Ninguno",
123122
"Socks4": "Socks4",
124123
"Socks5": "Socks5",
125124
"Socks5Auth": "Socks5 con autenticación",
126125
"HTTP": "HTTP",
127-
"HTTPAuth": "HTTP con autenticación"
126+
"HTTPAuth": "HTTP con autenticación",
127+
"WebServerSettings": "Configurações do servidor web"
128128
}

0 commit comments

Comments
 (0)