diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py
index 57ec26f37a..96e3231746 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 List, Optional
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
@@ -263,6 +265,8 @@ def __init__(self, handle, options, state=None, filename=None, magnet=None):
self.state = None
self.moving_storage_dest_path = None
self.tracker_status = ''
+ self.trackers_status = {}
+ self.trackers_peers = {}
self.tracker_host = None
self.forcing_recheck = False
self.forcing_recheck_paused = False
@@ -578,7 +582,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,17 +603,60 @@ 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 set_tracker_status(self, status):
+ 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: str, tracker_url: str, message: str = ''):
"""Sets the tracker status.
Args:
- status (str): The tracker status.
+ status: The tracker status.
+ tracker_url: The tracker url.
+ message: The message from tracker error/warning alerts
Emits:
TorrentTrackerStatusEvent upon tracker status change.
@@ -618,12 +665,35 @@ def set_tracker_status(self, status):
self.tracker_host = None
- if self.tracker_status != status:
- self.tracker_status = status
+ if self.state == 'Paused':
+ return
+
+ if self.trackers_status.get(tracker_url, {}).get('status') != status:
+ self.trackers_status[tracker_url] = {
+ 'status': status,
+ 'message': message,
+ }
+ self.tracker_status = f'{status}{f": {message}" if message else ""}'
component.get('EventManager').emit(
- TorrentTrackerStatusEvent(self.torrent_id, self.tracker_status)
+ TorrentTrackerStatusEvent(
+ self.torrent_id,
+ self.trackers_status[tracker_url],
+ )
)
+ def set_tracker_peers(self, peers: int, tracker_url: str):
+ """Sets the tracker peers amount
+
+ Args:
+ peers: The number of peers the tracker has.
+ tracker_url: The tracker url.
+ """
+ if self.state == 'Paused':
+ return
+
+ if self.trackers_peers.get(tracker_url) != peers:
+ self.trackers_peers[tracker_url] = peers
+
def merge_trackers(self, torrent_info):
"""Merges new trackers in torrent_info into torrent"""
log.info(
@@ -666,6 +736,8 @@ def update_state(self):
self.state = 'Queued'
elif session_paused or status.paused:
self.state = 'Paused'
+ self.trackers_peers = {}
+ self.trackers_status = {}
else:
self.state = LT_TORRENT_STATE_MAP.get(str(status.state), str(status.state))
@@ -1158,7 +1230,10 @@ def _create_status_funcs(self):
'tracker': lambda: self.status.current_tracker,
'tracker_host': self.get_tracker_host,
'trackers': lambda: self.trackers,
+ # Deprecated: Use trackers_status
'tracker_status': lambda: self.tracker_status,
+ 'trackers_status': lambda: self.trackers_status,
+ 'trackers_peers': lambda: self.trackers_peers,
'upload_payload_rate': lambda: self.status.upload_payload_rate,
'comment': lambda: decode_bytes(self.torrent_info.comment())
if self.has_metadata
diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py
index c43a7a262d..ee2119b42c 100644
--- a/deluge/core/torrentmanager.py
+++ b/deluge/core/torrentmanager.py
@@ -1347,7 +1347,9 @@ def on_alert_tracker_reply(self, alert):
return
# Set the tracker status for the torrent
- torrent.set_tracker_status('Announce OK')
+ torrent.set_tracker_status('Announce OK', alert.url)
+ # Set the amount of peers of the tracker
+ torrent.set_tracker_peers(alert.num_peers, alert.url)
# Check for peer information from the tracker, if none then send a scrape request.
torrent.get_lt_status()
@@ -1362,7 +1364,7 @@ def on_alert_tracker_announce(self, alert):
return
# Set the tracker status for the torrent
- torrent.set_tracker_status('Announce Sent')
+ torrent.set_tracker_status('Announce Sent', alert.url)
def on_alert_tracker_warning(self, alert):
"""Alert handler for libtorrent tracker_warning_alert"""
@@ -1371,7 +1373,11 @@ def on_alert_tracker_warning(self, alert):
except (RuntimeError, KeyError):
return
# Set the tracker status for the torrent
- torrent.set_tracker_status('Warning: %s' % decode_bytes(alert.message()))
+ torrent.set_tracker_status(
+ 'Warning',
+ alert.url,
+ decode_bytes(alert.warning_message()),
+ )
def on_alert_tracker_error(self, alert):
"""Alert handler for libtorrent tracker_error_alert"""
@@ -1384,19 +1390,19 @@ def on_alert_tracker_error(self, alert):
if not error_message:
error_message = decode_bytes(alert.error.message())
log.debug(
- 'Tracker Error Alert: %s [%s]', decode_bytes(alert.message()), error_message
+ f'Tracker Error Alert: {decode_bytes(alert.message())} [{error_message}]'
)
# 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
for endpoint in tracker['endpoints']
):
- torrent.set_tracker_status('Announce OK')
+ torrent.set_tracker_status('Announce OK', alert.url)
else:
- torrent.set_tracker_status('Error: ' + error_message)
+ torrent.set_tracker_status('Error', alert.url, error_message)
break
def on_alert_storage_moved(self, alert):
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': ''}]
diff --git a/deluge/ui/gtk3/details_tab.py b/deluge/ui/gtk3/details_tab.py
index 04a5eabfe0..a029dc9948 100644
--- a/deluge/ui/gtk3/details_tab.py
+++ b/deluge/ui/gtk3/details_tab.py
@@ -12,7 +12,7 @@
import deluge.component as component
from deluge.common import decode_bytes, fdate, fsize, is_url
-from .tab_data_funcs import fdate_or_dash, fpieces_num_size
+from .tab_data_funcs import fdate_or_dash, fpieces_num_size, fyes_no
from .torrentdetails import Tab
log = logging.getLogger(__name__)
@@ -34,6 +34,7 @@ def __init__(self):
self.add_tab_widget(
'summary_pieces', fpieces_num_size, ('num_pieces', 'piece_length')
)
+ self.add_tab_widget('summary_private', fyes_no, ('private',))
def update(self):
# Get the first selected torrent
diff --git a/deluge/ui/gtk3/glade/main_window.tabs.menu_trackers.ui b/deluge/ui/gtk3/glade/main_window.tabs.menu_trackers.ui
new file mode 100644
index 0000000000..0c6d7ae08b
--- /dev/null
+++ b/deluge/ui/gtk3/glade/main_window.tabs.menu_trackers.ui
@@ -0,0 +1,27 @@
+
+
+
['trackers', 'trackers_status', 'trackers_peers']+ */ + Trackers: ['trackers', 'trackers_status', 'trackers_peers'], + + /** + * Keys used in the trackers tab of the statistics panel for Deluge version <2.1.1. + *
['tracker_host', 'tracker_status']+ */ + TrackersRedundant: ['tracker_host', 'tracker_status'], + /** * Keys used in the details tab of the statistics panel. */ diff --git a/deluge/ui/web/js/deluge-all/UI.js b/deluge/ui/web/js/deluge-all/UI.js index f7edc84b19..1b3c10a527 100644 --- a/deluge/ui/web/js/deluge-all/UI.js +++ b/deluge/ui/web/js/deluge-all/UI.js @@ -52,6 +52,7 @@ deluge.ui = { deluge.sidebar = new Deluge.Sidebar(); deluge.statusbar = new Deluge.Statusbar(); deluge.toolbar = new Deluge.Toolbar(); + deluge.server_version = ''; this.detailsPanel = new Ext.Panel({ id: 'detailsPanel', @@ -223,6 +224,11 @@ deluge.ui = { this.running = setTimeout(this.update, 2000); this.update(); } + deluge.client.daemon.get_version({ + success: function (server_version) { + deluge.server_version = server_version; + }, + }); deluge.client.web.get_plugins({ success: this.onGotPlugins, scope: this, @@ -234,6 +240,7 @@ deluge.ui = { * @private */ onDisconnect: function () { + deluge.server_version = ''; this.stop(); }, diff --git a/deluge/ui/web/js/deluge-all/data/TrackerRecord.js b/deluge/ui/web/js/deluge-all/data/TrackerRecord.js new file mode 100644 index 0000000000..f8d65b97d5 --- /dev/null +++ b/deluge/ui/web/js/deluge-all/data/TrackerRecord.js @@ -0,0 +1,40 @@ +/** + * Deluge.data.TrackerRecord.js + * + * Copyright (c) Damien Churchill 2009-2010