summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2023-09-13 10:26:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2023-09-13 10:26:10 +0000
commitdb0e1d594c548180ea26fd7d771917737a8bf011 (patch)
treed3cda33ca956c206f0fbe626d63a3759f4986c40
parentReleasing debian version 2.1.2~dev0+20230529-4. (diff)
downloaddeluge-db0e1d594c548180ea26fd7d771917737a8bf011.tar.xz
deluge-db0e1d594c548180ea26fd7d771917737a8bf011.zip
Merging upstream version 2.1.2~dev0+20230827.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r--deluge/tests/data/v2_hybrid.torrentbin0 -> 613 bytes
-rw-r--r--deluge/tests/data/v2_test.torrentbin0 -> 345 bytes
-rw-r--r--deluge/tests/test_ui_common.py119
-rw-r--r--deluge/ui/common.py220
4 files changed, 316 insertions, 23 deletions
diff --git a/deluge/tests/data/v2_hybrid.torrent b/deluge/tests/data/v2_hybrid.torrent
new file mode 100644
index 0000000..e58057c
--- /dev/null
+++ b/deluge/tests/data/v2_hybrid.torrent
Binary files differ
diff --git a/deluge/tests/data/v2_test.torrent b/deluge/tests/data/v2_test.torrent
new file mode 100644
index 0000000..fe6cbd0
--- /dev/null
+++ b/deluge/tests/data/v2_test.torrent
Binary files differ
diff --git a/deluge/tests/test_ui_common.py b/deluge/tests/test_ui_common.py
index fc56ebc..87a4a2c 100644
--- a/deluge/tests/test_ui_common.py
+++ b/deluge/tests/test_ui_common.py
@@ -44,7 +44,7 @@ class TestUICommon:
ti = TorrentInfo(filename, filetree=1)
assert ti.files_tree == files_tree
- filestree2 = {
+ files_tree2 = {
'contents': {
'torrent_filehash': {
'type': 'dir',
@@ -71,7 +71,7 @@ class TestUICommon:
'type': 'dir',
}
ti = TorrentInfo(filename, filetree=2)
- assert ti.files_tree == filestree2
+ assert ti.files_tree == files_tree2
def test_hash_optional_md5sum(self):
# Ensure `md5sum` key is not included in filetree output
@@ -173,3 +173,118 @@ class TestUICommon:
}
]
assert ti.files == result_files
+
+ def test_bittorrent_v2_path(self):
+ filename = common.get_test_data_file('v2_test.torrent')
+ files_tree = {
+ 'torrent_test': {
+ 'small.txt': (0, 22, True),
+ '還在一個人無聊嗎~還不趕緊上來聊天美.txt': (1, 32, True),
+ }
+ }
+ ti = TorrentInfo(filename, filetree=1)
+ assert ti.files_tree == files_tree
+
+ files_tree2 = {
+ 'contents': {
+ 'torrent_test': {
+ 'type': 'dir',
+ 'contents': {
+ 'small.txt': {
+ 'type': 'file',
+ 'path': 'torrent_test/small.txt',
+ 'length': 22,
+ 'index': 0,
+ 'download': True,
+ },
+ '還在一個人無聊嗎~還不趕緊上來聊天美.txt': {
+ 'type': 'file',
+ 'path': 'torrent_test/還在一個人無聊嗎~還不趕緊上來聊天美.txt',
+ 'length': 32,
+ 'index': 1,
+ 'download': True,
+ },
+ },
+ 'length': 54,
+ 'download': True,
+ }
+ },
+ 'type': 'dir',
+ }
+ ti = TorrentInfo(filename, filetree=2)
+ assert ti.files_tree == files_tree2
+
+ def test_bittorrent_v2_hybrid_path(self):
+ filename = common.get_test_data_file('v2_hybrid.torrent')
+ files_tree = {
+ 'torrent_test': {
+ 'small.txt': (0, 22, True),
+ '還在一個人無聊嗎~還不趕緊上來聊天美.txt': (2, 32, True),
+ '.pad': {
+ '16362': (1, 16362, True),
+ '16352': (3, 16352, True),
+ },
+ }
+ }
+ ti = TorrentInfo(filename, filetree=1, force_bt_version=1)
+ assert ti.files_tree == files_tree
+ del files_tree['torrent_test']['.pad']
+ files_tree['torrent_test']['還在一個人無聊嗎~還不趕緊上來聊天美.txt'] = (1, 32, True)
+ ti = TorrentInfo(filename, filetree=1, force_bt_version=2)
+ assert ti.files_tree == files_tree
+
+ files_tree2 = {
+ 'contents': {
+ 'torrent_test': {
+ 'type': 'dir',
+ 'contents': {
+ 'small.txt': {
+ 'type': 'file',
+ 'path': 'torrent_test/small.txt',
+ 'length': 22,
+ 'index': 0,
+ 'download': True,
+ },
+ '還在一個人無聊嗎~還不趕緊上來聊天美.txt': {
+ 'type': 'file',
+ 'path': 'torrent_test/還在一個人無聊嗎~還不趕緊上來聊天美.txt',
+ 'length': 32,
+ 'index': 2,
+ 'download': True,
+ },
+ '.pad': {
+ 'type': 'dir',
+ 'contents': {
+ '16362': {
+ 'type': 'file',
+ 'path': 'torrent_test/.pad/16362',
+ 'length': 16362,
+ 'index': 1,
+ 'download': True,
+ },
+ '16352': {
+ 'type': 'file',
+ 'path': 'torrent_test/.pad/16352',
+ 'length': 16352,
+ 'index': 3,
+ 'download': True,
+ },
+ },
+ 'length': 32714,
+ 'download': True,
+ },
+ },
+ 'length': 32768,
+ 'download': True,
+ }
+ },
+ 'type': 'dir',
+ }
+ ti = TorrentInfo(filename, filetree=2, force_bt_version=1)
+ assert ti.files_tree == files_tree2
+ torrent_test = files_tree2['contents']['torrent_test']
+ torrent_test['length'] -= torrent_test['contents']['.pad']['length']
+ del torrent_test['contents']['.pad']
+ torrent_test['contents']['還在一個人無聊嗎~還不趕緊上來聊天美.txt']['index'] = 1
+ ti = TorrentInfo(filename, filetree=2, force_bt_version=2)
+ assert ti.files_tree == files_tree2
diff --git a/deluge/ui/common.py b/deluge/ui/common.py
index e9b445d..64d5ca2 100644
--- a/deluge/ui/common.py
+++ b/deluge/ui/common.py
@@ -13,6 +13,7 @@ The ui common module contains methods and classes that are deemed useful for all
import logging
import os
from hashlib import sha1 as sha
+from typing import Tuple
from deluge import bencode
from deluge.common import decode_bytes
@@ -171,10 +172,11 @@ class TorrentInfo:
filename (str, optional): The path to the .torrent file.
filetree (int, optional): The version of filetree to create (defaults to 1).
torrent_file (dict, optional): A bdecoded .torrent file contents.
+ force_bt_version (int, optional): The BitTorrent spec to use for parsing (defaults to 1).
"""
- def __init__(self, filename='', filetree=1, torrent_file=None):
+ def __init__(self, filename='', filetree=1, torrent_file=None, force_bt_version=1):
self._filedata = None
if torrent_file:
self._metainfo = torrent_file
@@ -211,9 +213,24 @@ class TorrentInfo:
else:
self._name = decode_bytes(info_dict['name'], encoding)
+ meta_version = info_dict['meta version'] if 'meta version' in info_dict else -1
+ is_hybrid = 'files' in info_dict and meta_version == 2
+
+ parse_v1 = False
+ parse_v2 = False
+ if is_hybrid:
+ if force_bt_version == 1:
+ parse_v1 = True
+ elif force_bt_version == 2:
+ parse_v2 = True
+ elif 'files' in info_dict:
+ parse_v1 = True
+ elif meta_version == 2 and 'file tree' in info_dict:
+ parse_v2 = True
+
# Get list of files from torrent info
self._files = []
- if 'files' in info_dict:
+ if parse_v1:
paths = {}
dirs = {}
prefix = self._name
@@ -245,25 +262,67 @@ class TorrentInfo:
if filetree == 2:
- def walk(path, item):
+ def walk(full_path, item):
if item['type'] == 'dir':
- item.update(dirs[path])
+ item.update(dirs[full_path])
else:
- item.update(paths[path])
+ item.update(paths[full_path])
item['download'] = True
file_tree = FileTree2(list(paths))
file_tree.walk(walk)
else:
- def walk(path, item):
+ def walk(full_path, item):
if isinstance(item, dict):
return item
- return [paths[path]['index'], paths[path]['length'], True]
+ return [paths[full_path]['index'], paths[full_path]['length'], True]
file_tree = FileTree(paths)
file_tree.walk(walk)
self._files_tree = file_tree.get_tree()
+ elif parse_v2:
+
+ def single_file_torrent(inner_info_dict):
+ if len(inner_info_dict['file tree']) > 1:
+ return False
+
+ file_name = [key for key in inner_info_dict['file tree']][0]
+ return inner_info_dict['name'] == file_name
+
+ if not single_file_torrent(info_dict):
+ info_dict['file tree'] = {info_dict['name']: info_dict['file tree']}
+
+ if filetree == 2:
+
+ def walk(full_path, item):
+ if item['type'] == 'file':
+ item['path'] = full_path
+ self._files.append(
+ {
+ 'path': full_path,
+ 'size': item['length'],
+ 'download': True,
+ }
+ )
+ item['download'] = True
+
+ file_tree = FileTree2BTv2(info_dict['file tree'])
+ file_tree.walk(walk)
+ else:
+
+ def walk(full_path, item):
+ if isinstance(item, dict):
+ return item
+ self._files.append(
+ {'path': full_path, 'size': item[1], 'download': True}
+ )
+ return [item[0], item[1], True]
+
+ file_tree = FiletreeBTv2(info_dict['file tree'])
+ file_tree.walk(walk)
+
+ self._files_tree = file_tree.get_tree()
else:
self._files.append(
{'path': self._name, 'size': info_dict['length'], 'download': True}
@@ -386,13 +445,31 @@ class TorrentInfo:
class FileTree2:
"""
- Converts a list of paths in to a file tree.
+ Converts a list of paths, from a V1 torrent, into a file tree.
+
+ Each file will have the dictionary structure of:
+ { file_name: {type, path, index, length, download} }
+ where:
+ type (str): will always be "file"
+ path (str): the absolute file path from the root the torrent
+ index (int): the index of file in the torrent
+ length (int): the size of the file, in bytes
+ download (bool): marks the file to download
+
+ Folder will be dictionaries of files:
+ { dir1: type, contents: {file_name1: {...}, file_name2: {...}}, dir2: ... }
+ where:
+ type (str): will always be "dir"
+ contents (dict): a dictionary of inner files and folders
+
+ The entire tree will start with a root dictionary:
+ { contents: {dirs...}, type: "dir" }
- :param paths: The paths to be converted
- :type paths: list
+ Args:
+ paths (list): The paths to be converted.
"""
- def __init__(self, paths):
+ def __init__(self, paths: list):
self.tree = {'contents': {}, 'type': 'dir'}
def get_parent(path):
@@ -466,13 +543,23 @@ class FileTree2:
class FileTree:
"""
- Convert a list of paths in a file tree.
+ Converts a dict of paths, from a V1 torrent, into a file tree.
+
+ Each file will have the dictionary structure of:
+ { file_name: [index, length, download] }
+ Where:
+ index (int): the index of file in the torrent
+ length (int): the size of the file, in bytes
+ download (bool): marks the file to download
+
+ Folder will be dictionaries of files:
+ { dir1: {file_name1: [...], file_name2: [...]}, dir2: ... }
- :param paths: The paths to be converted.
- :type paths: list
+ Args:
+ paths (dict): The paths to be converted.
"""
- def __init__(self, paths):
+ def __init__(self, paths: dict):
self.tree = {}
def get_parent(path):
@@ -498,8 +585,8 @@ class FileTree:
"""
Return the tree, after first converting all file lists to a tuple.
- :returns: the file tree.
- :rtype: dictionary
+ Returns:
+ dict: the file tree.
"""
def to_tuple(path, item):
@@ -515,10 +602,10 @@ class FileTree:
Walk through the file tree calling the callback function on each item
contained.
- :param callback: The function to be used as a callback, it should have
- the signature func(item, path) where item is a `tuple` for a file
- and `dict` for a directory.
- :type callback: function
+ Args:
+ callback (function): The function to be used as a callback, it should have
+ the signature func(item, path) where item is a `tuple` for a file
+ and `dict` for a directory.
"""
def walk(directory, parent_path):
@@ -547,3 +634,94 @@ class FileTree:
self.walk(write)
return '\n'.join(lines)
+
+
+class FiletreeBTv2(FileTree):
+ """
+ Converts a dict of paths, from a V2 torrent, into a file tree.
+
+ Each file will have the dictionary structure of:
+ { file_name: [index, length, download] }
+ Where:
+ index (int): the index of file in the torrent
+ length (int): the size of the file, in bytes
+ download (bool): marks the file to download
+
+ Folder will be dictionaries of files:
+ { dir1: {file_name1: [...], file_name2: [...]}, dir2: ... }
+
+ Args:
+ file_tree (dict): The paths to be converted.
+ """
+
+ def __init__(self, file_tree):
+ self.tree = {}
+
+ def get_parent(curr_tree_dict, index, parent) -> int:
+ for key, item in curr_tree_dict.items():
+ key = decode_bytes(key)
+ if b'' in item:
+ parent[key] = [index, item[b''][b'length']]
+ index += 1
+ else:
+ parent[key] = {}
+ index = get_parent(item, index, parent[key])
+ return index
+
+ get_parent(file_tree, 0, self.tree)
+
+
+class FileTree2BTv2(FileTree2):
+ """
+ Converts a dict of paths, from a V2 torrent, into a file tree.
+
+ Each file will have the dictionary structure of:
+ { file_name: {type, path, index, length, download} }
+ where:
+ type (str): will always be "file"
+ path (str): the absolute file path from the root the torrent
+ index (int): the index of file in the torrent
+ length (int): the size of the file, in bytes
+ download (bool): marks the file to download
+
+ Folder will be dictionaries of files:
+ { dir1: type, contents: {file_name1: {...}, file_name2: {...}}, dir2: ... }
+ where:
+ type (str): will always be "dir"
+ contents (dict): a dictionary of inner files and folders
+
+ The entire tree will start with a root dictionary:
+ { contents: {dirs...}, type: "dir" }
+
+ Args:
+ file_tree (dict): The paths to be converted.
+ """
+
+ def __init__(self, file_tree):
+ self.tree = {'contents': {}, 'type': 'dir'}
+
+ def get_parent(curr_tree_dict, index, parent) -> Tuple[int, int]:
+ total_length = 0
+ for key, item in curr_tree_dict.items():
+ key = decode_bytes(key)
+ if b'' in item:
+ length = item[b''][b'length']
+ total_length += length
+ parent['contents'][key] = {
+ 'index': index,
+ 'length': length,
+ 'type': 'file',
+ }
+ index += 1
+ else:
+ parent['contents'][key] = {
+ 'contents': {},
+ 'type': 'dir',
+ 'length': 0,
+ }
+ index, length = get_parent(item, index, parent['contents'][key])
+ parent['contents'][key]['length'] = length
+ total_length += length
+ return index, total_length
+
+ get_parent(file_tree, 0, self.tree)