From 1ff9031f3a26c33283b0d96bc808822b16fc8946 Mon Sep 17 00:00:00 2001 From: DjLegolas Date: Sat, 30 Apr 2022 13:15:45 +0300 Subject: [PATCH] [Torrent] Add trackers cache 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. --- deluge/core/torrent.py | 49 ++++++++++++++++++++++++++++++++--- deluge/core/torrentmanager.py | 2 +- deluge/tests/test_torrent.py | 28 ++++++++++++++++++++ 3 files changed, 75 insertions(+), 4 deletions(-) diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 57ec26f37a..1b52c86772 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -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 @@ -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 @@ -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 @@ -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. diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index c43a7a262d..3df309a550 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -1388,7 +1388,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 diff --git a/deluge/tests/test_torrent.py b/deluge/tests/test_torrent.py index 62886159ea..39620fe563 100644 --- a/deluge/tests/test_torrent.py +++ b/deluge/tests/test_torrent.py @@ -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': ''}]