summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCalum Lind <calumlind+deluge@gmail.com>2019-05-20 15:31:26 +0100
committerCalum Lind <calumlind+deluge@gmail.com>2019-05-20 16:49:25 +0100
commitc6b6902e9f3e37f5b15184eb509b48b43817a331 (patch)
tree6494447c1daab80e2b3eeb8291b7b4019234cf92
parent6a5bb44d5b20c1404237bab5d5051a7ff8a26a65 (diff)
downloaddeluge-c6b6902e9f3e37f5b15184eb509b48b43817a331.tar.gz
deluge-c6b6902e9f3e37f5b15184eb509b48b43817a331.tar.bz2
deluge-c6b6902e9f3e37f5b15184eb509b48b43817a331.zip
[Core] Fix prefetch magnets missing trackers
When adding magnets that have been prefetched the tracker details were lost. A result of returning only the lt.torrent_info.metadata which does not contain full torrent details, such as trackers. - Modified torrentmanager prefetch_metadata to return dict instead of base64 encoded bencoded metadata dict... - Used a namedtuple to ease identifying tuple contents. - Updated tests to reflect changes with mock trackers added to test_torrent.file.torrent. - Refactor TorrentInfo to accept dict instead of bytes and add a class method to accept metadata dict with lists of trackers. - Rename class arg from metainfo to torrent_file, matching lt.torrent_info. - Rename metadata property to correct name; metainfo. - Simplify class variable naming with _filedata and _metainfo for torrent file contents encoded and decoded respectively. - Update GTK Add torrent dialog to pass trackers to TorrentInfo.
-rw-r--r--deluge/core/core.py14
-rw-r--r--deluge/core/torrentmanager.py28
-rw-r--r--deluge/tests/test_core.py2
-rw-r--r--deluge/tests/test_torrentmanager.py61
-rw-r--r--deluge/ui/common.py77
-rw-r--r--deluge/ui/gtk3/addtorrentdialog.py15
6 files changed, 107 insertions, 90 deletions
diff --git a/deluge/core/core.py b/deluge/core/core.py
index 91f9b8a38..9a19e3057 100644
--- a/deluge/core/core.py
+++ b/deluge/core/core.py
@@ -437,22 +437,22 @@ class Core(component.Component):
@export
def prefetch_magnet_metadata(self, magnet, timeout=30):
- """Download the metadata for the magnet uri without adding torrent to deluge session.
+ """Download magnet metadata without adding to Deluge session.
+
+ Used by UIs to get magnet files for selection before adding to session.
Args:
magnet (str): The magnet uri.
- timeout (int): Time to wait to recieve metadata from peers.
+ timeout (int): Number of seconds to wait before cancelling request.
Returns:
- Deferred: A tuple of (torrent_id (str), metadata (bytes)) for the magnet.
-
- The metadata is base64 encoded.
+ Deferred: A tuple of (torrent_id (str), metadata (dict)) for the magnet.
"""
def on_metadata(result, result_d):
- torrent_id, metadata = result
- result_d.callback((torrent_id, b64encode(metadata)))
+ """Return result of torrent_id and metadata"""
+ result_d.callback(result)
return result
d = self.torrentmanager.prefetch_metadata(magnet, timeout)
diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py
index c92939c06..a7df5012b 100644
--- a/deluge/core/torrentmanager.py
+++ b/deluge/core/torrentmanager.py
@@ -15,6 +15,7 @@ import logging
import operator
import os
import time
+from collections import namedtuple
from tempfile import gettempdir
import six.moves.cPickle as pickle # noqa: N813
@@ -339,21 +340,20 @@ class TorrentManager(component.Component):
return torrent_info
def prefetch_metadata(self, magnet, timeout):
- """Download metadata for a magnet uri.
+ """Download the metadata for a magnet uri.
Args:
magnet (str): A magnet uri to download the metadata for.
- timeout (int): How long
+ timeout (int): Number of seconds to wait before cancelling.
Returns:
- Deferred: A tuple of (torrent_id (str), bencoded metadata (bytes))
+ Deferred: A tuple of (torrent_id (str), metadata (dict))
"""
torrent_id = get_magnet_info(magnet)['info_hash']
if torrent_id in self.prefetching_metadata:
- d = self.prefetching_metadata[torrent_id][0]
- return d
+ return self.prefetching_metadata[torrent_id].defer
add_torrent_params = {}
add_torrent_params['save_path'] = gettempdir()
@@ -374,7 +374,8 @@ class TorrentManager(component.Component):
# Cancel the defer if timeout reached.
defer_timeout = self.callLater(timeout, d.cancel)
d.addBoth(self.on_prefetch_metadata, torrent_id, defer_timeout)
- self.prefetching_metadata[torrent_id] = (d, torrent_handle)
+ Prefetch = namedtuple('Prefetch', 'defer handle')
+ self.prefetching_metadata[torrent_id] = Prefetch(defer=d, handle=torrent_handle)
return d
def on_prefetch_metadata(self, torrent_info, torrent_id, defer_timeout):
@@ -384,18 +385,18 @@ class TorrentManager(component.Component):
except error.AlreadyCalled:
pass
- log.debug('remove magnet from session')
+ log.debug('remove prefetch magnet from session')
try:
- torrent_handle = self.prefetching_metadata.pop(torrent_id)[1]
+ torrent_handle = self.prefetching_metadata.pop(torrent_id).handle
except KeyError:
pass
else:
self.session.remove_torrent(torrent_handle, 1)
- metadata = b''
+ metadata = None
if isinstance(torrent_info, lt.torrent_info):
- log.debug('metadata received')
- metadata = torrent_info.metadata()
+ log.debug('prefetch metadata received')
+ metadata = lt.bdecode(torrent_info.metadata())
return torrent_id, metadata
@@ -447,8 +448,7 @@ class TorrentManager(component.Component):
raise AddTorrentError('Torrent already being added (%s).' % torrent_id)
elif torrent_id in self.prefetching_metadata:
# Cancel and remove metadata fetching torrent.
- d = self.prefetching_metadata[torrent_id][0]
- d.cancel()
+ self.prefetching_metadata[torrent_id].defer.cancel()
# Check for renamed files and if so, rename them in the torrent_info before adding.
if options['mapped_files'] and torrent_info:
@@ -1545,7 +1545,7 @@ class TorrentManager(component.Component):
# Try callback to prefetch_metadata method.
try:
- d = self.prefetching_metadata[torrent_id][0]
+ d = self.prefetching_metadata[torrent_id].defer
except KeyError:
pass
else:
diff --git a/deluge/tests/test_core.py b/deluge/tests/test_core.py
index c0da54da5..4f64c73e6 100644
--- a/deluge/tests/test_core.py
+++ b/deluge/tests/test_core.py
@@ -314,7 +314,7 @@ class CoreTestCase(BaseTestCase):
def test_prefetch_metadata_existing(self):
"""Check another call with same magnet returns existing deferred."""
magnet = 'magnet:?xt=urn:btih:ab570cdd5a17ea1b61e970bb72047de141bce173'
- expected = ('ab570cdd5a17ea1b61e970bb72047de141bce173', b'')
+ expected = ('ab570cdd5a17ea1b61e970bb72047de141bce173', None)
def on_result(result):
self.assertEqual(result, expected)
diff --git a/deluge/tests/test_torrentmanager.py b/deluge/tests/test_torrentmanager.py
index 84ad5a5e9..bf84f451b 100644
--- a/deluge/tests/test_torrentmanager.py
+++ b/deluge/tests/test_torrentmanager.py
@@ -15,7 +15,6 @@ import pytest
from twisted.internet import defer, task
from deluge import component
-from deluge.bencode import bencode
from deluge.core.core import Core
from deluge.core.rpcserver import RPCServer
from deluge.error import InvalidTorrentError
@@ -72,36 +71,34 @@ class TorrentmanagerTestCase(BaseTestCase):
expected = (
'ab570cdd5a17ea1b61e970bb72047de141bce173',
- bencode(
- {
- 'piece length': 32768,
- 'sha1': (
- b'2\xce\xb6\xa8"\xd7\xf0\xd4\xbf\xdc^K\xba\x1bh'
- b'\x9d\xc5\xb7\xac\xdd'
- ),
- 'name': 'azcvsupdater_2.6.2.jar',
- 'private': 0,
- 'pieces': (
- b'\xdb\x04B\x05\xc3\'\xdab\xb8su97\xa9u'
- b'\xca<w\\\x1ef\xd4\x9b\x16\xa9}\xc0\x9f:\xfd'
- b'\x97qv\x83\xa2"\xef\x9d7\x0by!\rl\xe5v\xb7'
- b'\x18{\xf7/"P\xe9\x8d\x01D\x9e8\xbd\x16\xe3'
- b'\xfb-\x9d\xaa\xbcM\x11\xba\x92\xfc\x13F\xf0'
- b'\x1c\x86x+\xc8\xd0S\xa9\x90`\xa1\xe4\x82\xe8'
- b'\xfc\x08\xf7\xe3\xe5\xf6\x85\x1c%\xe7%\n\xed'
- b'\xc0\x1f\xa1;\x9a\xea\xcf\x90\x0c/F>\xdf\xdagA'
- b'\xc42|\xda\x82\xf5\xa6b\xa1\xb8#\x80wI\xd8f'
- b'\xf8\xbd\xacW\xab\xc3s\xe0\xbbw\xf2K\xbe\xee'
- b'\xa8rG\xe1W\xe8\xb7\xc2i\xf3\xd8\xaf\x9d\xdc'
- b'\xd0#\xf4\xc1\x12u\xcd\x0bE?:\xe8\x9c\x1cu'
- b'\xabb(oj\r^\xd5\xd5A\x83\x88\x9a\xa1J\x1c?'
- b'\xa1\xd6\x8c\x83\x9e&'
- ),
- 'length': 307949,
- 'name.utf-8': b'azcvsupdater_2.6.2.jar',
- 'ed2k': b'>p\xefl\xfa]\x95K\x1b^\xc2\\;;e\xb7',
- }
- ),
+ {
+ b'piece length': 32768,
+ b'sha1': (
+ b'2\xce\xb6\xa8"\xd7\xf0\xd4\xbf\xdc^K\xba\x1bh'
+ b'\x9d\xc5\xb7\xac\xdd'
+ ),
+ b'name': b'azcvsupdater_2.6.2.jar',
+ b'private': 0,
+ b'pieces': (
+ b'\xdb\x04B\x05\xc3\'\xdab\xb8su97\xa9u'
+ b'\xca<w\\\x1ef\xd4\x9b\x16\xa9}\xc0\x9f:\xfd'
+ b'\x97qv\x83\xa2"\xef\x9d7\x0by!\rl\xe5v\xb7'
+ b'\x18{\xf7/"P\xe9\x8d\x01D\x9e8\xbd\x16\xe3'
+ b'\xfb-\x9d\xaa\xbcM\x11\xba\x92\xfc\x13F\xf0'
+ b'\x1c\x86x+\xc8\xd0S\xa9\x90`\xa1\xe4\x82\xe8'
+ b'\xfc\x08\xf7\xe3\xe5\xf6\x85\x1c%\xe7%\n\xed'
+ b'\xc0\x1f\xa1;\x9a\xea\xcf\x90\x0c/F>\xdf\xdagA'
+ b'\xc42|\xda\x82\xf5\xa6b\xa1\xb8#\x80wI\xd8f'
+ b'\xf8\xbd\xacW\xab\xc3s\xe0\xbbw\xf2K\xbe\xee'
+ b'\xa8rG\xe1W\xe8\xb7\xc2i\xf3\xd8\xaf\x9d\xdc'
+ b'\xd0#\xf4\xc1\x12u\xcd\x0bE?:\xe8\x9c\x1cu'
+ b'\xabb(oj\r^\xd5\xd5A\x83\x88\x9a\xa1J\x1c?'
+ b'\xa1\xd6\x8c\x83\x9e&'
+ ),
+ b'length': 307949,
+ b'name.utf-8': b'azcvsupdater_2.6.2.jar',
+ b'ed2k': b'>p\xefl\xfa]\x95K\x1b^\xc2\\;;e\xb7',
+ },
)
self.assertEqual(expected, self.successResultOf(d))
@@ -109,7 +106,7 @@ class TorrentmanagerTestCase(BaseTestCase):
magnet = 'magnet:?xt=urn:btih:ab570cdd5a17ea1b61e970bb72047de141bce173'
d = self.tm.prefetch_metadata(magnet, 30)
self.clock.advance(30)
- expected = ('ab570cdd5a17ea1b61e970bb72047de141bce173', b'')
+ expected = ('ab570cdd5a17ea1b61e970bb72047de141bce173', None)
return d.addCallback(self.assertEqual, expected)
@pytest.mark.todo
diff --git a/deluge/ui/common.py b/deluge/ui/common.py
index 38c27d8cf..21bcafd5f 100644
--- a/deluge/ui/common.py
+++ b/deluge/ui/common.py
@@ -173,38 +173,36 @@ class TorrentInfo(object):
"""Collects information about a torrent file.
Args:
- filename (str): The path to the .torrent file.
+ filename (str, optional): The path to the .torrent file.
filetree (int, optional): The version of filetree to create (defaults to 1).
- metainfo (bytes, optional): A bencoded filedump from a .torrent file.
- metadata (bytes, optional): A bencoded metadata info_dict.
+ torrent_file (dict, optional): A bdecoded .torrent file contents.
"""
- def __init__(self, filename='', filetree=1, metainfo=None, metadata=None):
- # Get the torrent metainfo from the torrent file
- if metadata:
- self._metainfo_dict = {b'info': bencode.bdecode(metadata)}
-
- self._metainfo = bencode.bencode(self._metainfo_dict)
- else:
- self._metainfo = metainfo
- if filename and not self._metainfo:
- log.debug('Attempting to open %s.', filename)
- try:
- with open(filename, 'rb') as _file:
- self._metainfo = _file.read()
- except IOError as ex:
- log.warning('Unable to open %s: %s', filename, ex)
- return
+ def __init__(self, filename='', filetree=1, torrent_file=None):
+ self._filedata = None
+ if torrent_file:
+ self._metainfo = torrent_file
+ elif filename:
+ log.debug('Attempting to open %s.', filename)
+ try:
+ with open(filename, 'rb') as _file:
+ self._filedata = _file.read()
+ except IOError as ex:
+ log.warning('Unable to open %s: %s', filename, ex)
+ return
try:
- self._metainfo_dict = bencode.bdecode(self._metainfo)
+ self._metainfo = bencode.bdecode(self._filedata)
except bencode.BTFailure as ex:
log.warning('Failed to decode %s: %s', filename, ex)
return
+ else:
+ log.warning('Requires valid arguments.')
+ return
- # info_dict with keys decoded.
- info_dict = {k.decode(): v for k, v in self._metainfo_dict[b'info'].items()}
+ # info_dict with keys decoded to unicode.
+ info_dict = {k.decode(): v for k, v in self._metainfo[b'info'].items()}
self._info_hash = sha(bencode.bencode(info_dict)).hexdigest()
# Get encoding from torrent file if available
@@ -299,6 +297,25 @@ class TorrentInfo(object):
else:
self._files_tree = {self._name: (0, info_dict['length'], True)}
+ @classmethod
+ def from_metadata(cls, metadata, trackers=None):
+ """Create a TorrentInfo from metadata and trackers
+
+ Args:
+ metadata (dict): A bdecoded info section of torrent file.
+ trackers (list of lists, optional): The trackers to include.
+
+ """
+ if not isinstance(metadata, dict):
+ return
+
+ metainfo = {b'info': metadata}
+ if trackers:
+ metainfo[b'announce'] = trackers[0][0].encode('utf-8')
+ trackers_utf8 = [[t.encode('utf-8') for t in tier] for tier in trackers]
+ metainfo[b'announce-list'] = trackers_utf8
+ return cls(torrent_file=metainfo)
+
def as_dict(self, *keys):
"""The torrent info as a dictionary, filtered by keys.
@@ -358,24 +375,28 @@ class TorrentInfo(object):
return self._files_tree
@property
- def metadata(self):
- """The torrents metainfo dictionary.
+ def metainfo(self):
+ """Returns the torrent metainfo dictionary.
+
+ This is the bdecoded torrent file contents.
Returns:
- dict: The bdecoded metainfo dictionary.
+ dict: The metainfo dictionary.
"""
- return self._metainfo_dict
+ return self._metainfo
@property
def filedata(self):
"""The contents of the .torrent file.
Returns:
- str: The metainfo bencoded dictionary from a torrent file.
+ bytes: The bencoded metainfo.
"""
- return self._metainfo
+ if not self._filedata:
+ self._filedata = bencode.bencode(self._metainfo)
+ return self._filedata
class FileTree2(object):
diff --git a/deluge/ui/gtk3/addtorrentdialog.py b/deluge/ui/gtk3/addtorrentdialog.py
index d164c2cc5..e7066f9ba 100644
--- a/deluge/ui/gtk3/addtorrentdialog.py
+++ b/deluge/ui/gtk3/addtorrentdialog.py
@@ -11,7 +11,7 @@ from __future__ import division, unicode_literals
import logging
import os
-from base64 import b64decode, b64encode
+from base64 import b64encode
from xml.sax.saxutils import escape as xml_escape
from xml.sax.saxutils import unescape as xml_unescape
@@ -251,16 +251,15 @@ class AddTorrentDialog(component.Component):
if already_added:
self.show_already_added_dialog(already_added)
- def _on_uri_metadata(self, result, uri):
+ def _on_uri_metadata(self, result, uri, trackers):
"""Process prefetched metadata to allow file priority selection."""
- info_hash, b64_metadata = result
- log.debug('on_uri_metadata for %s (%s)', uri, info_hash)
+ info_hash, metadata = result
+ log.debug('magnet metadata for %s (%s)', uri, info_hash)
if info_hash not in self.prefetching_magnets:
return
- if b64_metadata:
- metadata = b64decode(b64_metadata)
- info = TorrentInfo(metadata=metadata)
+ if metadata:
+ info = TorrentInfo.from_metadata(metadata, [[t] for t in trackers])
self.files[info_hash] = info.files
self.infos[info_hash] = info.filedata
else:
@@ -313,7 +312,7 @@ class AddTorrentDialog(component.Component):
self.prefetching_magnets.append(torrent_id)
self.prefetch_waiting_message(torrent_id, None)
d = client.core.prefetch_magnet_metadata(uri)
- d.addCallback(self._on_uri_metadata, uri)
+ d.addCallback(self._on_uri_metadata, uri, magnet['trackers'])
d.addErrback(self._on_uri_metadata_fail, torrent_id)
if already_added: