Skip to content

Commit

Permalink
Automatically refresh and expire the torrent status cache.
Browse files Browse the repository at this point in the history
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: #369
  • Loading branch information
gazpachoking authored and cas-- committed Feb 15, 2022
1 parent 62a4052 commit 8ff4683
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 20 deletions.
40 changes: 32 additions & 8 deletions deluge/core/torrent.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import logging
import os
import socket
import time
from typing import Optional
from urllib.parse import urlparse

from twisted.internet.defer import Deferred, DeferredList
Expand Down Expand Up @@ -234,7 +236,8 @@ def __init__(self, handle, options, state=None, filename=None, magnet=None):
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
Expand Down Expand Up @@ -267,7 +270,6 @@ def __init__(self, handle, options, state=None, filename=None, magnet=None):
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()
Expand Down Expand Up @@ -641,7 +643,7 @@ def merge_trackers(self, torrent_info):

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()
Expand Down Expand Up @@ -709,7 +711,7 @@ def force_error_state(self, message, restart_to_resume=True):
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,
Expand Down Expand Up @@ -1024,7 +1026,7 @@ def get_status(self, keys, diff=False, update=False, all_keys=False):
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)
Expand Down Expand Up @@ -1054,13 +1056,35 @@ def get_status(self, keys, diff=False, update=False, all_keys=False):

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"""
Expand Down
15 changes: 4 additions & 11 deletions deluge/core/torrentmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,19 +279,14 @@ def update(self):
'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
):
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):
Expand Down Expand Up @@ -1359,10 +1354,8 @@ def on_alert_tracker_reply(self, alert):
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):
Expand Down Expand Up @@ -1612,7 +1605,7 @@ def on_alert_state_update(self, alert):
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())

Expand Down
19 changes: 18 additions & 1 deletion deluge/tests/test_torrent.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -356,3 +356,20 @@ def test_connect_peer_port(self):
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

0 comments on commit 8ff4683

Please sign in to comment.