From 63b25311f53e4e089d335858f749fe40423e8529 Mon Sep 17 00:00:00 2001 From: Calum Lind Date: Thu, 29 Jun 2017 12:13:35 +0100 Subject: [PATCH] [UI] Refactor TorrentInfo and add functionality Make a clearer distinction about torrent metainfo and metadata and allow passing these to TorrentInfo. --- deluge/ui/common.py | 186 +++++++++++++++++++++++--------------------- 1 file changed, 99 insertions(+), 87 deletions(-) diff --git a/deluge/ui/common.py b/deluge/ui/common.py index 71bbd0468..ac3a18904 100644 --- a/deluge/ui/common.py +++ b/deluge/ui/common.py @@ -183,60 +183,67 @@ DISK_CACHE_KEYS = [ class TorrentInfo(object): - """ - Collects information about a torrent file. + """Collects information about a torrent file. - :param filename: The path to the torrent - :type filename: string + Args: + 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): - # Get the torrent data from the torrent file - try: - log.debug('Attempting to open %s.', filename) - with open(filename, 'rb') as _file: - self.__m_filedata = _file.read() - except IOError as ex: - log.warning('Unable to open %s: %s', filename, ex) - raise ex - try: - self.__m_metadata = bencode.bdecode(self.__m_filedata) - except bencode.BTFailure as ex: - log.warning('Failed to decode %s: %s', filename, ex) - raise ex + def __init__(self, filename='', filetree=1, metainfo=None, metadata=None): + # Get the torrent metainfo from the torrent file + if metadata: + self._metainfo_dict = {'info': bencode.bdecode(metadata)} + 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 - 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 - self.encoding = None - if 'encoding' in self.__m_metadata: - self.encoding = self.__m_metadata['encoding'] - elif 'codepage' in self.__m_metadata: - self.encoding = str(self.__m_metadata['codepage']) - if not self.encoding: - self.encoding = 'UTF-8' + encoding = self._metainfo_dict.get('encoding', None) + codepage = self._metainfo_dict.get('codepage', None) + if not encoding: + encoding = codepage if codepage else 'UTF-8' - # Check if 'name.utf-8' is in the torrent and if not try to decode the string - # using the encoding found. - if 'name.utf-8' in self.__m_metadata['info']: - self.__m_name = decode_bytes(self.__m_metadata['info']['name.utf-8']) + # Decode 'name' with encoding unless 'name.utf-8' found. + if 'name.utf-8' in info_dict: + self._name = decode_bytes(info_dict['name.utf-8']) 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 - paths = {} - dirs = {} - if 'files' in self.__m_metadata['info']: - prefix = '' - if len(self.__m_metadata['info']['files']) > 1: - prefix = self.__m_name + if 'files' in info_dict: + paths = {} + dirs = {} + prefix = self._name if len(info_dict['files']) > 1 else '' - for index, f in enumerate(self.__m_metadata['info']['files']): + for index, f in enumerate(info_dict['files']): 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'] 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['index'] = index if 'sha1' in f and len(f['sha1']) == 20: @@ -270,84 +277,86 @@ class TorrentInfo(object): file_tree = FileTree(paths) file_tree.walk(walk) - self.__m_files_tree = file_tree.get_tree() + self._files_tree = file_tree.get_tree() else: if filetree == 2: - self.__m_files_tree = { + self._files_tree = { 'contents': { - self.__m_name: { + self._name: { 'type': 'file', 'index': 0, - 'length': self.__m_metadata['info']['length'], + 'length': info_dict['length'], 'download': True, - }, - }, + } + } } else: - self.__m_files_tree = { - self.__m_name: (0, self.__m_metadata['info']['length'], True), + self._files_tree = { + self._name: (0, info_dict['length'], True), } - self.__m_files = [] - if 'files' in self.__m_metadata['info']: + self._files = [] + if 'files' in info_dict: prefix = '' - if len(self.__m_metadata['info']['files']) > 1: - prefix = self.__m_name + if len(info_dict['files']) > 1: + prefix = self._name - for f in self.__m_metadata['info']['files']: - self.__m_files.append({ + for f in info_dict['files']: + self._files.append({ 'path': f['path'], 'size': f['length'], 'download': True, }) else: - self.__m_files.append({ - 'path': self.__m_name, - 'size': self.__m_metadata['info']['length'], + self._files.append({ + 'path': self._name, + 'size': info_dict['length'], 'download': True, }) def as_dict(self, *keys): - """ - Return the torrent info as a dictionary, only including the passed in - keys. + """The torrent info as a dictionary, filtered by keys. - :param keys: a number of key strings - :type keys: string + Args: + 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} @property 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 def info_hash(self): - """ - The torrents info_hash + """The calculated torrent info_hash. - :rtype: string + Returns: + str: The torrent info_hash. """ - return self.__m_info_hash + return self._info_hash @property def files(self): - """ - A list of the files that the torrent contains. + """The files that the torrent contains. + + Returns: + list: The list of torrent files. - :rtype: list """ - return self.__m_files + return self._files @property def files_tree(self): - """ - A dictionary based tree of the files. + """A tree of the files the torrent contains. :: @@ -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 def metadata(self): - """ - The torrents metadata. + """The torrents metainfo dictionary. + + Returns: + dict: The bdecoded metainfo dictionary. - :rtype: dictionary """ - return self.__m_metadata + return self._metainfo_dict @property def filedata(self): - """ - The torrents file data. This will be the bencoded dictionary read - from the torrent file. + """The contents of 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):