Files
addon/lib/torrentool/bencode.py
marco a8d5fad1a8 KoD 1.0
- completato il supporto ai torrent e aggiunto ilcorsaronero.xyz
- aggiunto supporto agli episodi locali, ovvero poter inserire nella libreria di kodi un misto tra puntate di kod e file scaricati altrove
- le viste ora si salvano di nuovo dal menu laterale, ma rimangono salvate per il tipo di contenuto visualizzato e non per il singolo menu
- ripensato il menu rapido, che ora è più rapido, ridisegnate alcune finestre
2020-05-14 21:26:48 +02:00

205 lines
5.8 KiB
Python

from collections import OrderedDict
from operator import itemgetter
from codecs import encode
from sys import version_info
from .exceptions import BencodeDecodingError, BencodeEncodingError
PY3 = version_info >= (3, 0)
if PY3:
str_type = str
byte_types = (bytes, bytearray)
chr_ = chr
int_types = int
else:
str_type = basestring
byte_types = bytes
chr_ = lambda ch: ch
int_types = (int, long)
class Bencode(object):
"""Exposes utilities for bencoding."""
@classmethod
def encode(cls, value):
"""Encodes a value into bencoded bytes.
:param value: Python object to be encoded (str, int, list, dict).
:param str val_encoding: Encoding used by strings in a given object.
:rtype: bytes
"""
val_encoding = 'utf-8'
def encode_str(v):
try:
v_enc = encode(v, val_encoding)
except UnicodeDecodeError:
if PY3:
raise
else:
# Suppose bytestring
v_enc = v
prefix = encode('%s:' % len(v_enc), val_encoding)
return prefix + v_enc
def encode_(val):
if isinstance(val, str_type):
result = encode_str(val)
elif isinstance(val, int_types):
result = encode(('i%se' % val), val_encoding)
elif isinstance(val, (list, set, tuple)):
result = encode('l', val_encoding)
for item in val:
result += encode_(item)
result += encode('e', val_encoding)
elif isinstance(val, dict):
result = encode('d', val_encoding)
# Dictionaries are expected to be sorted by key.
for k, v in OrderedDict(sorted(val.items(), key=itemgetter(0))).items():
result += (encode_str(k) + encode_(v))
result += encode('e', val_encoding)
elif isinstance(val, byte_types):
result = encode('%s:' % len(val), val_encoding)
result += val
else:
raise BencodeEncodingError('Unable to encode `%s` %s' % (type(val), val))
return result
return encode_(value)
@classmethod
def decode(cls, encoded):
"""Decodes bencoded data introduced as bytes.
Returns decoded structure(s).
:param bytes encoded:
"""
def create_dict(items):
# Let's guarantee that dictionaries are sorted.
k_v_pair = zip(*[iter(items)] * 2)
return OrderedDict(sorted(k_v_pair, key=itemgetter(0)))
def create_list(items):
return list(items)
stack_items = []
stack_containers = []
def compress_stack():
target_container = stack_containers.pop()
subitems = []
while True:
subitem = stack_items.pop()
subitems.append(subitem)
if subitem is target_container:
break
container_creator = subitems.pop()
container = container_creator(reversed(subitems))
stack_items.append(container)
def parse_forward(till_char, sequence):
number = ''
char_sub_idx = 0
for char_sub_idx, char_sub in enumerate(sequence):
char_sub = chr_(char_sub)
if char_sub == till_char:
break
number += char_sub
number = int(number or 0)
char_sub_idx += 1
return number, char_sub_idx
while encoded:
char = encoded[0]
char = chr_(char)
if char == 'd': # Dictionary
stack_items.append(create_dict)
stack_containers.append(create_dict)
encoded = encoded[1:]
elif char == 'l': # List
stack_items.append(create_list)
stack_containers.append(create_list)
encoded = encoded[1:]
elif char == 'i': # Integer
number, char_sub_idx = parse_forward('e', encoded[1:])
char_sub_idx += 1
stack_items.append(number)
encoded = encoded[char_sub_idx:]
elif char.isdigit(): # String
str_len, char_sub_idx = parse_forward(':', encoded)
last_char_idx = char_sub_idx + str_len
string = encoded[char_sub_idx:last_char_idx]
try:
string = string.decode('utf-8')
except UnicodeDecodeError:
# Considered bytestring (e.g. `pieces` hashes concatenation).
pass
stack_items.append(string)
encoded = encoded[last_char_idx:]
elif char == 'e': # End of a dictionary or a list.
compress_stack()
encoded = encoded[1:]
else:
raise BencodeDecodingError('Unable to interpret `%s` char.' % char)
if len(stack_items) == 1:
stack_items = stack_items.pop()
return stack_items
@classmethod
def read_string(cls, string):
"""Decodes a given bencoded string or bytestring.
Returns decoded structure(s).
:param str string:
:rtype: list
"""
if PY3 and not isinstance(string, byte_types):
string = string.encode()
return cls.decode(string)
@classmethod
def read_file(cls, filepath):
"""Decodes bencoded data of a given file.
Returns decoded structure(s).
:param str filepath:
:rtype: list
"""
with open(filepath, mode='rb') as f:
contents = f.read()
return cls.decode(contents)