Skip to content

Commit

Permalink
[Torrent] Add trackers cache
Browse files Browse the repository at this point in the history
The current way the trackers are being update is either by adding a new
torrent to the session (on startup or new one), or by changing the
trackers.
This leads to data not being updated in the `torrent.trackers` dict.
To solve this, a cache mechanism was added which will update the dict
on changes or every 5 seconds.
This will help us also get an update regarding `lt.announce_endpoint` in
 each of the trackers.
  • Loading branch information
DjLegolas committed Sep 25, 2024
1 parent 8df36c4 commit cef257d
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 4 deletions.
49 changes: 46 additions & 3 deletions deluge/core/torrent.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import os
import socket
import time
from typing import Optional
from typing import Optional, List
from urllib.parse import urlparse

from twisted.internet.defer import Deferred, DeferredList
Expand Down Expand Up @@ -238,6 +238,8 @@ def __init__(self, handle, options, state=None, filename=None, magnet=None):
self.magnet = magnet
self._status: Optional['lt.torrent_status'] = None
self._status_last_update: float = 0.0
self._trackers: Optional[List['lt.announce_entry']] = None
self._trackers_last_update: float = 0.0

self.torrent_info = self.handle.torrent_file()
self.has_metadata = self.status.has_metadata
Expand Down Expand Up @@ -578,7 +580,7 @@ def set_trackers(self, trackers=None):
trackers (list of dicts): A list of trackers.
"""
if trackers is None:
self.trackers = list(self.handle.trackers())
self.trackers = self.handle.trackers()
self.tracker_host = None
return

Expand All @@ -599,12 +601,53 @@ def set_trackers(self, trackers=None):
for tracker in self.handle.trackers():
log.debug(' [tier %s]: %s', tracker['tier'], tracker['url'])
# Set the tracker list in the torrent object
self.trackers = trackers
self.trackers = self.handle.trackers()
if len(trackers) > 0:
# Force a re-announce if there is at least 1 tracker
self.force_reannounce()
self.tracker_host = None

def get_lt_trackers(self):
"""Get the torrent trackers fresh, not from cache.
This should be used when a guaranteed fresh trackers is needed rather than
`torrent.handle.tracker()` because it will update the cache as well.
"""
trackers = self.handle.trackers()
self.trackers = trackers
return trackers

@property
def trackers(self) -> List[dict]:
"""Cached copy of the libtorrent Trackers for this torrent.
If it has not been updated within the last five seconds, it will be
automatically refreshed.
"""
if self._trackers_last_update < (time.time() - 5):
self.trackers = self.handle.trackers()
trackers_list = []
for tracker in self._trackers:
torrent_tracker = {
'url': '',
'message': '',
'tier': 0,
}
for data_key in torrent_tracker:
torrent_tracker[data_key] = tracker.get(data_key)
trackers_list.append(torrent_tracker)
return trackers_list

@trackers.setter
def trackers(self, trackers: List['lt.announce_entry']) -> None:
"""Updates the cached status.
Args:
trackers: a libtorrent torrent trackers
"""
self._trackers = trackers
self._trackers_last_update = time.time()

def set_tracker_status(self, status):
"""Sets the tracker status.
Expand Down
2 changes: 1 addition & 1 deletion deluge/core/torrentmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -1389,7 +1389,7 @@ def on_alert_tracker_error(self, alert):
)
# libtorrent 1.2 added endpoint struct to each tracker. to prevent false updates
# we will need to verify that at least one endpoint to the errored tracker is working
for tracker in torrent.handle.trackers():
for tracker in torrent.get_lt_trackers():
if tracker['url'] == alert.url:
if any(
endpoint['last_error']['value'] == 0
Expand Down
28 changes: 28 additions & 0 deletions deluge/tests/test_torrent.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,3 +386,31 @@ def test_status_cache(self):
# Advance time and verify cache expires and updates
mock_time.return_value += 10
assert torrent.status == 2

def test_trackers_cache(self):
atp = self.get_torrent_atp('test_torrent.file.torrent')
handle = self.session.add_torrent(atp)
mock_time = mock.Mock(return_value=time.time())
with mock.patch('time.time', mock_time):
torrent = Torrent(handle, {})
counter = itertools.count()
handle.trackers = mock.Mock(
side_effect=lambda: [
{'tier': counter.__next__(), 'url': '', 'message': ''}
]
)
first_trackers = torrent.get_lt_trackers()
assert first_trackers == [
{'tier': 0, 'url': '', 'message': ''}
], 'sanity check'
assert first_trackers == torrent.trackers, 'cached trackers should be used'
torrent.set_trackers()
assert torrent.trackers == [
{'tier': 1, 'url': '', 'message': ''}
], 'trackers should update'
assert torrent.get_lt_trackers() == [
{'tier': 2, 'url': '', 'message': ''}
], 'trackers should update a second time'
# Advance time and verify cache expires and updates
mock_time.return_value += 10
assert torrent.trackers == [{'tier': 3, 'url': '', 'message': ''}]

0 comments on commit cef257d

Please sign in to comment.