summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChase Sterling <chase.sterling@gmail.com>2022-02-08 14:34:02 -0500
committerCalum Lind <calumlind+deluge@gmail.com>2022-02-15 15:14:40 +0000
commit8ff4683780921111f26fe051e0274aac8afe8bf3 (patch)
treecb1c6a142ceae8860001c676a8d570ab451ddb8b
parent62a40521789eab273415550ea741ed668b2e947e (diff)
downloaddeluge-8ff4683780921111f26fe051e0274aac8afe8bf3.tar.gz
deluge-8ff4683780921111f26fe051e0274aac8afe8bf3.tar.bz2
deluge-8ff4683780921111f26fe051e0274aac8afe8bf3.zip
Automatically refresh and expire the torrent status cache.
Stop at ratio was not working when no clients were connected, because it was using a cached version of the torrent status, and never calling for a refresh. When a client connected, it called for the refresh and started working properly. Closes: https://dev.deluge-torrent.org/ticket/3497 Closes: https://github.com/deluge-torrent/deluge/pull/369
-rw-r--r--deluge/core/torrent.py40
-rw-r--r--deluge/core/torrentmanager.py15
-rw-r--r--deluge/tests/test_torrent.py19
3 files changed, 54 insertions, 20 deletions
diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py
index 9a4af0b8a..57ec26f37 100644
--- a/deluge/core/torrent.py
+++ b/deluge/core/torrent.py
@@ -16,6 +16,8 @@ Attributes:
import logging
import os
import socket
+import time
+from typing import Optional
from urllib.parse import urlparse
from twisted.internet.defer import Deferred, DeferredList
@@ -234,7 +236,8 @@ class Torrent:
self.handle = handle
self.magnet = magnet
- self.status = self.handle.status()
+ self._status: Optional['lt.torrent_status'] = None
+ self._status_last_update: float = 0.0
self.torrent_info = self.handle.torrent_file()
self.has_metadata = self.status.has_metadata
@@ -267,7 +270,6 @@ class Torrent:
self.prev_status = {}
self.waiting_on_folder_rename = []
- self.update_status(self.handle.status())
self._create_status_funcs()
self.set_options(self.options)
self.update_state()
@@ -641,7 +643,7 @@ class Torrent:
def update_state(self):
"""Updates the state, based on libtorrent's torrent state"""
- status = self.handle.status()
+ status = self.get_lt_status()
session_paused = component.get('Core').session.is_paused()
old_state = self.state
self.set_status_message()
@@ -709,7 +711,7 @@ class Torrent:
restart_to_resume (bool, optional): Prevent resuming clearing the error, only restarting
session can resume.
"""
- status = self.handle.status()
+ status = self.get_lt_status()
self._set_handle_flags(
flag=lt.torrent_flags.auto_managed,
set_flag=False,
@@ -1024,7 +1026,7 @@ class Torrent:
dict: a dictionary of the status keys and their values
"""
if update:
- self.update_status(self.handle.status())
+ self.get_lt_status()
if all_keys:
keys = list(self.status_funcs)
@@ -1054,13 +1056,35 @@ class Torrent:
return status_dict
- def update_status(self, status):
+ def get_lt_status(self) -> 'lt.torrent_status':
+ """Get the torrent status fresh, not from cache.
+
+ This should be used when a guaranteed fresh status is needed rather than
+ `torrent.handle.status()` because it will update the cache as well.
+ """
+ self.status = self.handle.status()
+ return self.status
+
+ @property
+ def status(self) -> 'lt.torrent_status':
+ """Cached copy of the libtorrent status for this torrent.
+
+ If it has not been updated within the last five seconds, it will be
+ automatically refreshed.
+ """
+ if self._status_last_update < (time.time() - 5):
+ self.status = self.handle.status()
+ return self._status
+
+ @status.setter
+ def status(self, status: 'lt.torrent_status') -> None:
"""Updates the cached status.
Args:
- status (libtorrent.torrent_status): a libtorrent torrent status
+ status: a libtorrent torrent status
"""
- self.status = status
+ self._status = status
+ self._status_last_update = time.time()
def _create_status_funcs(self):
"""Creates the functions for getting torrent status"""
diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py
index 753bc894c..41e1966b0 100644
--- a/deluge/core/torrentmanager.py
+++ b/deluge/core/torrentmanager.py
@@ -279,11 +279,6 @@ class TorrentManager(component.Component):
'Paused',
'Queued',
):
- # If the global setting is set, but the per-torrent isn't...
- # Just skip to the next torrent.
- # This is so that a user can turn-off the stop at ratio option on a per-torrent basis
- if not torrent.options['stop_at_ratio']:
- continue
if (
torrent.get_ratio() >= torrent.options['stop_ratio']
and torrent.is_finished
@@ -291,7 +286,7 @@ class TorrentManager(component.Component):
if torrent.options['remove_at_ratio']:
self.remove(torrent_id)
break
- if not torrent.handle.status().paused:
+ if not torrent.status.paused:
torrent.pause()
def __getitem__(self, torrent_id):
@@ -1359,10 +1354,8 @@ class TorrentManager(component.Component):
torrent.set_tracker_status('Announce OK')
# Check for peer information from the tracker, if none then send a scrape request.
- if (
- alert.handle.status().num_complete == -1
- or alert.handle.status().num_incomplete == -1
- ):
+ torrent.get_lt_status()
+ if torrent.status.num_complete == -1 or torrent.status.num_incomplete == -1:
torrent.scrape_tracker()
def on_alert_tracker_announce(self, alert):
@@ -1612,7 +1605,7 @@ class TorrentManager(component.Component):
except RuntimeError:
continue
if torrent_id in self.torrents:
- self.torrents[torrent_id].update_status(t_status)
+ self.torrents[torrent_id].status = t_status
self.handle_torrents_status_callback(self.torrents_status_requests.pop())
diff --git a/deluge/tests/test_torrent.py b/deluge/tests/test_torrent.py
index 6c07531dd..36adc0fde 100644
--- a/deluge/tests/test_torrent.py
+++ b/deluge/tests/test_torrent.py
@@ -3,7 +3,7 @@
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
-
+import itertools
import os
import time
from base64 import b64encode
@@ -356,3 +356,20 @@ class TestTorrent(BaseTestCase):
self.torrent = Torrent(handle, {})
assert not self.torrent.connect_peer('127.0.0.1', 'text')
assert self.torrent.connect_peer('127.0.0.1', '1234')
+
+ def test_status_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.status = mock.Mock(side_effect=counter.__next__)
+ first_status = torrent.get_lt_status()
+ assert first_status == 0, 'sanity check'
+ assert first_status == torrent.status, 'cached status should be used'
+ assert torrent.get_lt_status() == 1, 'status should update'
+ assert torrent.status == 1
+ # Advance time and verify cache expires and updates
+ mock_time.return_value += 10
+ assert torrent.status == 2