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
This commit is contained in:
parent
62a4052178
commit
8ff4683780
|
@ -16,6 +16,8 @@ Attributes:
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import socket
|
import socket
|
||||||
|
import time
|
||||||
|
from typing import Optional
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from twisted.internet.defer import Deferred, DeferredList
|
from twisted.internet.defer import Deferred, DeferredList
|
||||||
|
@ -234,7 +236,8 @@ class Torrent:
|
||||||
self.handle = handle
|
self.handle = handle
|
||||||
|
|
||||||
self.magnet = magnet
|
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.torrent_info = self.handle.torrent_file()
|
||||||
self.has_metadata = self.status.has_metadata
|
self.has_metadata = self.status.has_metadata
|
||||||
|
@ -267,7 +270,6 @@ class Torrent:
|
||||||
self.prev_status = {}
|
self.prev_status = {}
|
||||||
self.waiting_on_folder_rename = []
|
self.waiting_on_folder_rename = []
|
||||||
|
|
||||||
self.update_status(self.handle.status())
|
|
||||||
self._create_status_funcs()
|
self._create_status_funcs()
|
||||||
self.set_options(self.options)
|
self.set_options(self.options)
|
||||||
self.update_state()
|
self.update_state()
|
||||||
|
@ -641,7 +643,7 @@ class Torrent:
|
||||||
|
|
||||||
def update_state(self):
|
def update_state(self):
|
||||||
"""Updates the state, based on libtorrent's torrent state"""
|
"""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()
|
session_paused = component.get('Core').session.is_paused()
|
||||||
old_state = self.state
|
old_state = self.state
|
||||||
self.set_status_message()
|
self.set_status_message()
|
||||||
|
@ -709,7 +711,7 @@ class Torrent:
|
||||||
restart_to_resume (bool, optional): Prevent resuming clearing the error, only restarting
|
restart_to_resume (bool, optional): Prevent resuming clearing the error, only restarting
|
||||||
session can resume.
|
session can resume.
|
||||||
"""
|
"""
|
||||||
status = self.handle.status()
|
status = self.get_lt_status()
|
||||||
self._set_handle_flags(
|
self._set_handle_flags(
|
||||||
flag=lt.torrent_flags.auto_managed,
|
flag=lt.torrent_flags.auto_managed,
|
||||||
set_flag=False,
|
set_flag=False,
|
||||||
|
@ -1024,7 +1026,7 @@ class Torrent:
|
||||||
dict: a dictionary of the status keys and their values
|
dict: a dictionary of the status keys and their values
|
||||||
"""
|
"""
|
||||||
if update:
|
if update:
|
||||||
self.update_status(self.handle.status())
|
self.get_lt_status()
|
||||||
|
|
||||||
if all_keys:
|
if all_keys:
|
||||||
keys = list(self.status_funcs)
|
keys = list(self.status_funcs)
|
||||||
|
@ -1054,13 +1056,35 @@ class Torrent:
|
||||||
|
|
||||||
return status_dict
|
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.
|
"""Updates the cached status.
|
||||||
|
|
||||||
Args:
|
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):
|
def _create_status_funcs(self):
|
||||||
"""Creates the functions for getting torrent status"""
|
"""Creates the functions for getting torrent status"""
|
||||||
|
|
|
@ -279,11 +279,6 @@ class TorrentManager(component.Component):
|
||||||
'Paused',
|
'Paused',
|
||||||
'Queued',
|
'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 (
|
if (
|
||||||
torrent.get_ratio() >= torrent.options['stop_ratio']
|
torrent.get_ratio() >= torrent.options['stop_ratio']
|
||||||
and torrent.is_finished
|
and torrent.is_finished
|
||||||
|
@ -291,7 +286,7 @@ class TorrentManager(component.Component):
|
||||||
if torrent.options['remove_at_ratio']:
|
if torrent.options['remove_at_ratio']:
|
||||||
self.remove(torrent_id)
|
self.remove(torrent_id)
|
||||||
break
|
break
|
||||||
if not torrent.handle.status().paused:
|
if not torrent.status.paused:
|
||||||
torrent.pause()
|
torrent.pause()
|
||||||
|
|
||||||
def __getitem__(self, torrent_id):
|
def __getitem__(self, torrent_id):
|
||||||
|
@ -1359,10 +1354,8 @@ class TorrentManager(component.Component):
|
||||||
torrent.set_tracker_status('Announce OK')
|
torrent.set_tracker_status('Announce OK')
|
||||||
|
|
||||||
# Check for peer information from the tracker, if none then send a scrape request.
|
# Check for peer information from the tracker, if none then send a scrape request.
|
||||||
if (
|
torrent.get_lt_status()
|
||||||
alert.handle.status().num_complete == -1
|
if torrent.status.num_complete == -1 or torrent.status.num_incomplete == -1:
|
||||||
or alert.handle.status().num_incomplete == -1
|
|
||||||
):
|
|
||||||
torrent.scrape_tracker()
|
torrent.scrape_tracker()
|
||||||
|
|
||||||
def on_alert_tracker_announce(self, alert):
|
def on_alert_tracker_announce(self, alert):
|
||||||
|
@ -1612,7 +1605,7 @@ class TorrentManager(component.Component):
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
continue
|
continue
|
||||||
if torrent_id in self.torrents:
|
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())
|
self.handle_torrents_status_callback(self.torrents_status_requests.pop())
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
# the additional special exception to link portions of this program with the OpenSSL library.
|
# the additional special exception to link portions of this program with the OpenSSL library.
|
||||||
# See LICENSE for more details.
|
# See LICENSE for more details.
|
||||||
#
|
#
|
||||||
|
import itertools
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
|
@ -356,3 +356,20 @@ class TestTorrent(BaseTestCase):
|
||||||
self.torrent = Torrent(handle, {})
|
self.torrent = Torrent(handle, {})
|
||||||
assert not self.torrent.connect_peer('127.0.0.1', 'text')
|
assert not self.torrent.connect_peer('127.0.0.1', 'text')
|
||||||
assert self.torrent.connect_peer('127.0.0.1', '1234')
|
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
|
||||||
|
|
Loading…
Reference in New Issue