1
1
from __future__ import annotations
2
2
3
+ import logging
3
4
import mimetypes
4
5
from asyncio import get_event_loop , shield
5
6
from binascii import hexlify , unhexlify
7
+ from functools import lru_cache
6
8
from pathlib import Path , PurePosixPath
9
+ from time import time
7
10
from typing import TYPE_CHECKING , Any , Optional , TypedDict , cast
8
11
9
12
import libtorrent as lt
39
42
TOTAL = "total"
40
43
LOADED = "loaded"
41
44
ALL_LOADED = "all_loaded"
45
+ logger = logging .getLogger (__name__ )
42
46
43
47
44
48
class JSONFilesInfo (TypedDict ):
@@ -53,6 +57,21 @@ class JSONFilesInfo(TypedDict):
53
57
progress : float
54
58
55
59
60
+ @lru_cache (maxsize = 1 )
61
+ def cached_read (tracker_file : str , _ : int ) -> list [bytes ]:
62
+ """
63
+ Keep one cache for one tracker file at a time (by default: for a max of 120 seconds, see caller).
64
+
65
+ When adding X torrents at once, this avoids reading the same file X times.
66
+ """
67
+ try :
68
+ with open (tracker_file , "rb" ) as f :
69
+ return [line .rstrip () for line in f if line .rstrip ()] # uTorrent format contains blank lines between URLs
70
+ except OSError :
71
+ logger .exception ("Failed to read tracker file!" )
72
+ return []
73
+
74
+
56
75
class DownloadsEndpoint (RESTEndpoint ):
57
76
"""
58
77
This endpoint is responsible for all requests regarding downloads. Examples include getting all downloads,
@@ -359,6 +378,17 @@ async def get_downloads(self, request: Request) -> RESTResponse: # noqa: C901
359
378
result .append (info )
360
379
return RESTResponse ({"downloads" : result , "checkpoints" : checkpoints })
361
380
381
+ def _get_default_trackers (self ) -> list [bytes ]:
382
+ """
383
+ Get the default trackers from the configured tracker file.
384
+
385
+ Tracker file format is "(<TRACKER><NEWLINE><NEWLINE>)*". We assume "<TRACKER>" does not include newlines.
386
+ """
387
+ tracker_file = self .download_manager .config .get ("libtorrent/download_defaults/trackers_file" )
388
+ if not tracker_file :
389
+ return []
390
+ return cached_read (tracker_file , int (time ())// 120 )
391
+
362
392
@docs (
363
393
tags = ["Libtorrent" ],
364
394
summary = "Start a download from a provided URI." ,
@@ -397,7 +427,7 @@ async def get_downloads(self, request: Request) -> RESTResponse: # noqa: C901
397
427
"uri*" : (String , "The URI of the torrent file that should be downloaded. This URI can either represent a file "
398
428
"location, a magnet link or a HTTP(S) url." ),
399
429
}))
400
- async def add_download (self , request : Request ) -> RESTResponse : # noqa: C901
430
+ async def add_download (self , request : Request ) -> RESTResponse : # noqa: C901, PLR0912
401
431
"""
402
432
Start a download from a provided URI.
403
433
"""
@@ -436,8 +466,11 @@ async def add_download(self, request: Request) -> RESTResponse: # noqa: C901
436
466
try :
437
467
if tdef :
438
468
download = await self .download_manager .start_download (tdef = tdef , config = download_config )
439
- elif uri :
469
+ else : # guaranteed to have uri
440
470
download = await self .download_manager .start_download_from_uri (uri , config = download_config )
471
+ if self .download_manager .config .get ("libtorrent/download_defaults/trackers_file" ):
472
+ await download .get_handle () # We can only add trackers to a valid handle, wait for it.
473
+ download .add_trackers (self ._get_default_trackers ())
441
474
except Exception as e :
442
475
return RESTResponse ({"error" : {
443
476
"handled" : True ,
0 commit comments