[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.
This commit is contained in:
parent
6a5bb44d5b
commit
c6b6902e9f
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,16 +71,15 @@ class TorrentmanagerTestCase(BaseTestCase):
|
|||
|
||||
expected = (
|
||||
'ab570cdd5a17ea1b61e970bb72047de141bce173',
|
||||
bencode(
|
||||
{
|
||||
'piece length': 32768,
|
||||
'sha1': (
|
||||
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'
|
||||
),
|
||||
'name': 'azcvsupdater_2.6.2.jar',
|
||||
'private': 0,
|
||||
'pieces': (
|
||||
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'
|
||||
|
@ -97,11 +95,10 @@ class TorrentmanagerTestCase(BaseTestCase):
|
|||
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'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
|
||||
|
|
|
@ -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:
|
||||
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._metainfo = _file.read()
|
||||
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):
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue