[UI] Refactor TorrentInfo and add functionality

Make a clearer distinction about torrent metainfo and metadata and allow
passing these to TorrentInfo.
This commit is contained in:
Calum Lind 2017-06-29 12:13:35 +01:00
parent d45dbfe064
commit 63b25311f5
1 changed files with 99 additions and 87 deletions

View File

@ -183,60 +183,67 @@ DISK_CACHE_KEYS = [
class TorrentInfo(object): class TorrentInfo(object):
""" """Collects information about a torrent file.
Collects information about a torrent file.
:param filename: The path to the torrent Args:
:type filename: string filename (str): 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.
""" """
def __init__(self, filename, filetree=1): def __init__(self, filename='', filetree=1, metainfo=None, metadata=None):
# Get the torrent data from the torrent file # Get the torrent metainfo from the torrent file
try: if metadata:
log.debug('Attempting to open %s.', filename) self._metainfo_dict = {'info': bencode.bdecode(metadata)}
with open(filename, 'rb') as _file: else:
self.__m_filedata = _file.read() self._metainfo = metainfo
except IOError as ex: if filename and not self._metainfo:
log.warning('Unable to open %s: %s', filename, ex) log.debug('Attempting to open %s.', filename)
raise ex try:
try: with open(filename, 'rb') as _file:
self.__m_metadata = bencode.bdecode(self.__m_filedata) self._metainfo = _file.read()
except bencode.BTFailure as ex: except IOError as ex:
log.warning('Failed to decode %s: %s', filename, ex) log.warning('Unable to open %s: %s', filename, ex)
raise ex return
self.__m_info_hash = sha(bencode.bencode(self.__m_metadata['info'])).hexdigest() try:
self._metainfo_dict = bencode.bdecode(self._metainfo)
except bencode.BTFailure as ex:
log.warning('Failed to decode %s: %s', filename, ex)
return
info_dict = self._metainfo_dict['info']
self._info_hash = sha(bencode.bencode(info_dict)).hexdigest()
# Get encoding from torrent file if available # Get encoding from torrent file if available
self.encoding = None encoding = self._metainfo_dict.get('encoding', None)
if 'encoding' in self.__m_metadata: codepage = self._metainfo_dict.get('codepage', None)
self.encoding = self.__m_metadata['encoding'] if not encoding:
elif 'codepage' in self.__m_metadata: encoding = codepage if codepage else 'UTF-8'
self.encoding = str(self.__m_metadata['codepage'])
if not self.encoding:
self.encoding = 'UTF-8'
# Check if 'name.utf-8' is in the torrent and if not try to decode the string # Decode 'name' with encoding unless 'name.utf-8' found.
# using the encoding found. if 'name.utf-8' in info_dict:
if 'name.utf-8' in self.__m_metadata['info']: self._name = decode_bytes(info_dict['name.utf-8'])
self.__m_name = decode_bytes(self.__m_metadata['info']['name.utf-8'])
else: else:
self.__m_name = decode_bytes(self.__m_metadata['info']['name'], self.encoding) self._name = decode_bytes(info_dict['name'], encoding)
# Get list of files from torrent info # Get list of files from torrent info
paths = {} if 'files' in info_dict:
dirs = {} paths = {}
if 'files' in self.__m_metadata['info']: dirs = {}
prefix = '' prefix = self._name if len(info_dict['files']) > 1 else ''
if len(self.__m_metadata['info']['files']) > 1:
prefix = self.__m_name
for index, f in enumerate(self.__m_metadata['info']['files']): for index, f in enumerate(info_dict['files']):
if 'path.utf-8' in f: if 'path.utf-8' in f:
path = decode_bytes(os.path.join(prefix, *f['path.utf-8'])) path = decode_bytes(os.path.join(*f['path.utf-8']))
del f['path.utf-8'] del f['path.utf-8']
else: else:
path = os.path.join(prefix, decode_bytes(os.path.join(*f['path']), self.encoding)) path = decode_bytes(os.path.join(*f['path']), encoding)
if prefix:
path = os.path.join(prefix, path)
f['path'] = path f['path'] = path
f['index'] = index f['index'] = index
if 'sha1' in f and len(f['sha1']) == 20: if 'sha1' in f and len(f['sha1']) == 20:
@ -270,84 +277,86 @@ class TorrentInfo(object):
file_tree = FileTree(paths) file_tree = FileTree(paths)
file_tree.walk(walk) file_tree.walk(walk)
self.__m_files_tree = file_tree.get_tree() self._files_tree = file_tree.get_tree()
else: else:
if filetree == 2: if filetree == 2:
self.__m_files_tree = { self._files_tree = {
'contents': { 'contents': {
self.__m_name: { self._name: {
'type': 'file', 'type': 'file',
'index': 0, 'index': 0,
'length': self.__m_metadata['info']['length'], 'length': info_dict['length'],
'download': True, 'download': True,
}, }
}, }
} }
else: else:
self.__m_files_tree = { self._files_tree = {
self.__m_name: (0, self.__m_metadata['info']['length'], True), self._name: (0, info_dict['length'], True),
} }
self.__m_files = [] self._files = []
if 'files' in self.__m_metadata['info']: if 'files' in info_dict:
prefix = '' prefix = ''
if len(self.__m_metadata['info']['files']) > 1: if len(info_dict['files']) > 1:
prefix = self.__m_name prefix = self._name
for f in self.__m_metadata['info']['files']: for f in info_dict['files']:
self.__m_files.append({ self._files.append({
'path': f['path'], 'path': f['path'],
'size': f['length'], 'size': f['length'],
'download': True, 'download': True,
}) })
else: else:
self.__m_files.append({ self._files.append({
'path': self.__m_name, 'path': self._name,
'size': self.__m_metadata['info']['length'], 'size': info_dict['length'],
'download': True, 'download': True,
}) })
def as_dict(self, *keys): def as_dict(self, *keys):
""" """The torrent info as a dictionary, filtered by keys.
Return the torrent info as a dictionary, only including the passed in
keys.
:param keys: a number of key strings Args:
:type keys: string keys (str): A space-separated string of keys.
Returns:
dict: The torrent info dict with specified keys.
""" """
return {key: getattr(self, key) for key in keys} return {key: getattr(self, key) for key in keys}
@property @property
def name(self): def name(self):
""" """The name of the torrent.
The name of the torrent.
Returns:
str: The torrent name.
:rtype: string
""" """
return self.__m_name return self._name
@property @property
def info_hash(self): def info_hash(self):
""" """The calculated torrent info_hash.
The torrents info_hash
:rtype: string Returns:
str: The torrent info_hash.
""" """
return self.__m_info_hash return self._info_hash
@property @property
def files(self): def files(self):
""" """The files that the torrent contains.
A list of the files that the torrent contains.
Returns:
list: The list of torrent files.
:rtype: list
""" """
return self.__m_files return self._files
@property @property
def files_tree(self): def files_tree(self):
""" """A tree of the files the torrent contains.
A dictionary based tree of the files.
:: ::
@ -357,28 +366,31 @@ class TorrentInfo(object):
} }
} }
:rtype: dictionary Returns:
dict: The tree of files.
""" """
return self.__m_files_tree return self._files_tree
@property @property
def metadata(self): def metadata(self):
""" """The torrents metainfo dictionary.
The torrents metadata.
Returns:
dict: The bdecoded metainfo dictionary.
:rtype: dictionary
""" """
return self.__m_metadata return self._metainfo_dict
@property @property
def filedata(self): def filedata(self):
""" """The contents of the .torrent file.
The torrents file data. This will be the bencoded dictionary read
from the torrent file. Returns:
str: The metainfo bencoded dictionary from a torrent file.
:rtype: string
""" """
return self.__m_filedata return self._metainfo
class FileTree2(object): class FileTree2(object):