summaryrefslogtreecommitdiffstats
path: root/deluge/core/torrent.py
diff options
context:
space:
mode:
Diffstat (limited to 'deluge/core/torrent.py')
-rw-r--r--deluge/core/torrent.py148
1 files changed, 97 insertions, 51 deletions
diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py
index ede5200f0..57ec26f37 100644
--- a/deluge/core/torrent.py
+++ b/deluge/core/torrent.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
#
@@ -14,11 +13,12 @@ Attributes:
"""
-from __future__ import division, unicode_literals
-
import logging
import os
import socket
+import time
+from typing import Optional
+from urllib.parse import urlparse
from twisted.internet.defer import Deferred, DeferredList
@@ -34,18 +34,6 @@ from deluge.event import (
TorrentTrackerStatusEvent,
)
-try:
- from urllib.parse import urlparse
-except ImportError:
- # PY2 fallback
- from urlparse import urlparse # pylint: disable=ungrouped-imports
-
-try:
- from future_builtins import zip
-except ImportError:
- # Ignore on Py3.
- pass
-
log = logging.getLogger(__name__)
LT_TORRENT_STATE_MAP = {
@@ -94,7 +82,7 @@ def convert_lt_files(files):
"""Indexes and decodes files from libtorrent get_files().
Args:
- files (list): The libtorrent torrent files.
+ files (file_storage): The libtorrent torrent files.
Returns:
list of dict: The files.
@@ -109,18 +97,18 @@ def convert_lt_files(files):
}
"""
filelist = []
- for index, _file in enumerate(files):
+ for index in range(files.num_files()):
try:
- file_path = _file.path.decode('utf8')
+ file_path = files.file_path(index).decode('utf8')
except AttributeError:
- file_path = _file.path
+ file_path = files.file_path(index)
filelist.append(
{
'index': index,
'path': file_path.replace('\\', '/'),
- 'size': _file.size,
- 'offset': _file.offset,
+ 'size': files.file_size(index),
+ 'offset': files.file_offset(index),
}
)
@@ -161,7 +149,7 @@ class TorrentOptions(dict):
"""
def __init__(self):
- super(TorrentOptions, self).__init__()
+ super().__init__()
config = ConfigManager('core.conf').config
options_conf_map = {
'add_paused': 'add_paused',
@@ -191,14 +179,14 @@ class TorrentOptions(dict):
self['seed_mode'] = False
-class TorrentError(object):
+class TorrentError:
def __init__(self, error_message, was_paused=False, restart_to_resume=False):
self.error_message = error_message
self.was_paused = was_paused
self.restart_to_resume = restart_to_resume
-class Torrent(object):
+class Torrent:
"""Torrent holds information about torrents added to the libtorrent session.
Args:
@@ -248,9 +236,10 @@ class Torrent(object):
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.get_torrent_info()
+ self.torrent_info = self.handle.torrent_file()
self.has_metadata = self.status.has_metadata
self.options = TorrentOptions()
@@ -281,7 +270,6 @@ class Torrent(object):
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()
@@ -289,6 +277,18 @@ class Torrent(object):
if log.isEnabledFor(logging.DEBUG):
log.debug('Torrent object created.')
+ def _set_handle_flags(self, flag: lt.torrent_flags, set_flag: bool):
+ """set or unset a flag to the lt handle
+
+ Args:
+ flag (lt.torrent_flags): the flag to set/unset
+ set_flag (bool): True for setting the flag, False for unsetting it
+ """
+ if set_flag:
+ self.handle.set_flags(flag)
+ else:
+ self.handle.unset_flags(flag)
+
def on_metadata_received(self):
"""Process the metadata received alert for this torrent"""
self.has_metadata = True
@@ -373,7 +373,7 @@ class Torrent(object):
"""Sets maximum download speed for this torrent.
Args:
- m_up_speed (float): Maximum download speed in KiB/s.
+ m_down_speed (float): Maximum download speed in KiB/s.
"""
self.options['max_download_speed'] = m_down_speed
if m_down_speed < 0:
@@ -405,7 +405,7 @@ class Torrent(object):
return
# A list of priorities for each piece in the torrent
- priorities = self.handle.piece_priorities()
+ priorities = self.handle.get_piece_priorities()
def get_file_piece(idx, byte_offset):
return self.torrent_info.map_file(idx, byte_offset, 0).piece
@@ -438,7 +438,10 @@ class Torrent(object):
sequential (bool): Enable sequential downloading.
"""
self.options['sequential_download'] = sequential
- self.handle.set_sequential_download(sequential)
+ self._set_handle_flags(
+ flag=lt.torrent_flags.sequential_download,
+ set_flag=sequential,
+ )
def set_auto_managed(self, auto_managed):
"""Set auto managed mode, i.e. will be started or queued automatically.
@@ -448,7 +451,10 @@ class Torrent(object):
"""
self.options['auto_managed'] = auto_managed
if not (self.status.paused and not self.status.auto_managed):
- self.handle.auto_managed(auto_managed)
+ self._set_handle_flags(
+ flag=lt.torrent_flags.auto_managed,
+ set_flag=auto_managed,
+ )
self.update_state()
def set_super_seeding(self, super_seeding):
@@ -458,7 +464,10 @@ class Torrent(object):
super_seeding (bool): Enable super seeding.
"""
self.options['super_seeding'] = super_seeding
- self.handle.super_seeding(super_seeding)
+ self._set_handle_flags(
+ flag=lt.torrent_flags.super_seeding,
+ set_flag=super_seeding,
+ )
def set_stop_ratio(self, stop_ratio):
"""The seeding ratio to stop (or remove) the torrent at.
@@ -519,7 +528,7 @@ class Torrent(object):
self.handle.prioritize_files(file_priorities)
else:
log.debug('Unable to set new file priorities.')
- file_priorities = self.handle.file_priorities()
+ file_priorities = self.handle.get_file_priorities()
if 0 in self.options['file_priorities']:
# Previously marked a file 'skip' so check for any 0's now >0.
@@ -569,7 +578,7 @@ class Torrent(object):
trackers (list of dicts): A list of trackers.
"""
if trackers is None:
- self.trackers = [tracker for tracker in self.handle.trackers()]
+ self.trackers = list(self.handle.trackers())
self.tracker_host = None
return
@@ -634,7 +643,7 @@ class Torrent(object):
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()
@@ -646,7 +655,10 @@ class Torrent(object):
elif status_error:
self.state = 'Error'
# auto-manage status will be reverted upon resuming.
- self.handle.auto_managed(False)
+ self._set_handle_flags(
+ flag=lt.torrent_flags.auto_managed,
+ set_flag=False,
+ )
self.set_status_message(decode_bytes(status_error))
elif status.moving_storage:
self.state = 'Moving'
@@ -699,8 +711,11 @@ class Torrent(object):
restart_to_resume (bool, optional): Prevent resuming clearing the error, only restarting
session can resume.
"""
- status = self.handle.status()
- self.handle.auto_managed(False)
+ status = self.get_lt_status()
+ self._set_handle_flags(
+ flag=lt.torrent_flags.auto_managed,
+ set_flag=False,
+ )
self.forced_error = TorrentError(message, status.paused, restart_to_resume)
if not status.paused:
self.handle.pause()
@@ -714,7 +729,10 @@ class Torrent(object):
log.error('Restart deluge to clear this torrent error')
if not self.forced_error.was_paused and self.options['auto_managed']:
- self.handle.auto_managed(True)
+ self._set_handle_flags(
+ flag=lt.torrent_flags.auto_managed,
+ set_flag=True,
+ )
self.forced_error = None
self.set_status_message('OK')
if update_state:
@@ -838,7 +856,7 @@ class Torrent(object):
'client': client,
'country': country,
'down_speed': peer.payload_down_speed,
- 'ip': '%s:%s' % (peer.ip[0], peer.ip[1]),
+ 'ip': f'{peer.ip[0]}:{peer.ip[1]}',
'progress': peer.progress,
'seed': peer.flags & peer.seed,
'up_speed': peer.payload_up_speed,
@@ -857,7 +875,7 @@ class Torrent(object):
def get_file_priorities(self):
"""Return the file priorities"""
- if not self.handle.has_metadata():
+ if not self.handle.status().has_metadata:
return []
if not self.options['file_priorities']:
@@ -910,7 +928,7 @@ class Torrent(object):
# Check if hostname is an IP address and just return it if that's the case
try:
socket.inet_aton(host)
- except socket.error:
+ except OSError:
pass
else:
# This is an IP address because an exception wasn't raised
@@ -946,10 +964,10 @@ class Torrent(object):
if self.has_metadata:
# Use the top-level folder as torrent name.
- filename = decode_bytes(self.torrent_info.file_at(0).path)
+ filename = decode_bytes(self.torrent_info.files().file_path(0))
name = filename.replace('\\', '/', 1).split('/', 1)[0]
else:
- name = decode_bytes(self.handle.name())
+ name = decode_bytes(self.handle.status().name)
if not name:
name = self.torrent_id
@@ -1008,7 +1026,7 @@ class Torrent(object):
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)
@@ -1038,13 +1056,35 @@ class Torrent(object):
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"""
@@ -1166,7 +1206,10 @@ class Torrent(object):
"""
# Turn off auto-management so the torrent will not be unpaused by lt queueing
- self.handle.auto_managed(False)
+ self._set_handle_flags(
+ flag=lt.torrent_flags.auto_managed,
+ set_flag=False,
+ )
if self.state == 'Error':
log.debug('Unable to pause torrent while in Error state')
elif self.status.paused:
@@ -1201,7 +1244,10 @@ class Torrent(object):
else:
# Check if torrent was originally being auto-managed.
if self.options['auto_managed']:
- self.handle.auto_managed(True)
+ self._set_handle_flags(
+ flag=lt.torrent_flags.auto_managed,
+ set_flag=True,
+ )
try:
self.handle.resume()
except RuntimeError as ex:
@@ -1305,7 +1351,7 @@ class Torrent(object):
try:
with open(filepath, 'wb') as save_file:
save_file.write(filedump)
- except IOError as ex:
+ except OSError as ex:
log.error('Unable to save torrent file to: %s', ex)
filepath = os.path.join(get_config_dir(), 'state', self.torrent_id + '.torrent')